How to make thread safe code block in swift - ios

I tried multiple solution/answers given on stackoverflow but none of them worked for me. Some of them is as below :
https://stackoverflow.com/a/30495424/3145189
Is this safe to call wait() of DispatchSemaphore several times at one time?
https://stackoverflow.com/a/37155631/3145189
I am trying to achieve very simple thing, code block or function should execute serially, regardless of from which thread it's been called.
My Example code :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
DispatchQueue.global().async {
self.testLog(name:"first")
}
DispatchQueue.global().async {
self.testLog(name:"second")
}
DispatchQueue.main.async {
self.testLog(name: "mainthread")
}
}
func testLog(name:String) -> Void {
for i in 1..<1000 {
print("thread test \(i) name =\(name)")
}
}
So output should be like -
first thread call
thread test 1 name =first
thread test 2 name =first
thread test 3 name =first
.
.
.
thread test 999 name =first
second thread call
thread test 1 name =second
thread test 2 name =second
.
.
.
thread test 999 name =second
main thread call
thread test 1 name =mainthread
thread test 2 name =mainthread
.
.
.
thread test 999 name =mainthread
If function is called on first thread, it should continue print log for the first thread only. Order of thread can vary I don't care means even if it's print mainthread log first then second and first doesn't matter logs should be grouped.

This will execute the calls serially.
Keep a reference to serialQueue and you can submit blocks from any thread.
let serialQueue = DispatchQueue(label: "serial_queue")
serialQueue.async {
self.testLog(name: "first")
}
serialQueue.async {
self.testLog(name: "second")
}
serialQueue.async {
self.testLog(name: "third")
}

I am trying to achieve very simple thing, code block or function should execute serially, regardless of from which thread it's been called.
To execute serially you use a Dispatch serial queue. If you were writing a class or struct you could use a static let at class/struct level to store your queue in which your serialising function could dispatch to. A static let in this case is equivalent to a "class variable" in some languages.
If you were writing in (Objective-)C such variables can also be declared at the function level, that is a variable with global lifetime but with scope limited to within the function. Swift does not support these within a function, but you can scope a struct to a function...
func testLog(name:String) -> Void
{
struct LocalStatics
{
static let privateQueue = DispatchQueue(label: "testLogQueue")
}
// run the function body on the serial queue - could use async here
// and the body would still run not interleaved with other calls but
// the caller need not wait for it to do so
LocalStatics.privateQueue.sync {
for i in 1..<1000
{
print("thread test \(i) name =\(name)")
}
}
}
(For a debate on "local statics" in Swift see this SO Q&A)

Related

Swift - async/await - Execute async method on background thread from MainActor Task

Here's my viewDidLoad method :
func viewDidLoad() {
// Stuff here
Task { #MainActor in
doSomeWorkOnMainThread1()
doSomeWorkOnMainThread2()
await doSomeBackgroundWork()
doSomeWorkOnMainThread3()
doSomeWorkOnMainThread4()
}
}
And here's my method that should execute work on a background thread :
func doSomeBackgroundWork() async {
// can add some code here
// long image processing task
assert(!Thread.isMainThread)
// can add some code here
}
Is it possible to execute doSomeBackgroundWork on a background thread (without using GCD), and wait for it before going back to the main thread ?
You are looking for Task.sleep. Note that it is in nanoseconds which is Really Weird, so I have written an extension to switch to seconds; to get it, you must use a Double, not an Int:
extension Task where Success == Never, Failure == Never {
static func sleep(_ seconds:Double) async {
await self.sleep(UInt64(seconds * 1_000_000_000))
}
static func sleepThrowing(_ seconds:Double) async throws {
try await self.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
}
}
The way to guarantee that a piece of work will be done on a background thread is to give that work to an Actor. That is what an Actor is for (in part).
So for example if an Actor has a method doSleep that calls Task.sleep, then if you instantiate that actor and call that method from your Task, it will sleep on a background thread.
This solution is working, but it's not perfect I guess :
func doSomeBackgroundWork() async {
await Task {
// long image processing task
assert(!Thread.isMainThread)
}.result
}

How do I perform background tasks while blocking the caller

I'd like to create a function that performs multiple background operations but the caller should not be aware of its asynchronous nature. So when a caller calls that function it should block the caller's thread and continue after it finishes all the tasks.
Ideally, the function should be called by just invoking its name (say blockingFunction()).
How do I achieve that?
(The main thread isn't a concern here)
We will posit the following test method:
func test() {
print("start")
self.doYourThing()
print("finish")
}
That function is internally synchronous: it proceeds one line at a time from start to finish.
We also have an asynchronous method using an old-fashioned completion handler:
func behaveAsynchronously(completion: #escaping () -> ()) {
DispatchQueue.global().asyncAfter(deadline: .now()+10) {
completion()
}
}
We will consider the problem solved if doYourThing somehow calls behaveAsynchronously and yet "finish" prints 10 seconds after "start". Ready? Here we go:
func doYourThing() {
let group = DispatchGroup()
group.enter()
self.behaveAsynchronously {
group.leave()
}
group.wait()
}
QED.
Note that we are blocking the main thread for 10 seconds, which is illegal; if you did that in real life, you'd crash. Also, there must be multiple threads in the story, or we would be attempting to wait on the same thread we are delayed on and a deadlock results.

Synchronise async tasks in a serial queue

let serialQueue = DispatchQueue(label: "Serial Queue")
func performCriticalSectionTask() {
serialQueue.async {
performLongRuningAsyncTask()
}
}
func performLongRuningAsyncTask() {
/// some long running task
}
The function performCriticalSectionTask() can be called from different places many times.
I want this function to be running one at a time. Thus, I kept the critical section of code inside the serial async queue.
But, the problem here is that the critical section itself is a performLongRuningAsyncTask() which will return immediately, and thus serial queue will not wait for the current task to complete first and will start another one.
How can I solve this problem?
if performLongRuningAsyncTask is only running in one thread, it will be called only once at the time. In your case it delegates it to another thread, so you wrapping it into another thread call doesn't work since it will be on another thread anyway
You could do checks in the method itself, the simplest way is to add a boolean. (Or you could add these checks in your class that executes this method, with a completion handler).
Another ways are adding dispatch groups / semaphores / locks.
If you still need it to be executed later, you should use a dispatch group / OperationQueue / Semaphore.
func performLongRunningAsyncTask() {
self.serialQueue.sync {
if isAlreadyRunning {
return
}
isAlreadyRunning = true
}
asyncTask { result in
self.serialQueue.sync {
self.isAlreadyRunning = false
}
}
}

Swift serial queue die in runtime

In my app i have a task with a lot of mathematics. If i run this task in main queue, i have frozen for a few seconds screen after each call of task, but it work. If i run task in other queue - from some random iteration it do nothing. If i run code in main queue, i get debug message in every iteration, in other queue - i get no one after random iteration. Looks like, the queue dying for some reasons. Usage of cpu and memory doesn't change and stay at level 50-70%. I think about endless loop, deadlock or something similar in function, but in main queue it always work fine. What goes wrong?
class MyClass {
let serialQueue = DispatchQueue(
label: "com.notrealcompany.hardMathematics",
qos: .userInteractive
)
func doStuff() {
serialQueue.async {
node.getArea()
debugPrint("get area call")
}
}
serialQueue is an instance variable, but situation don't change.
It sounds like serialQueue is being deallocated when the method your code is in returns. Try moving serialQueue's declaration to an instance variable instead of a local variable.
class MyClass {
let serialQueue = DispatchQueue(
label: "com.notrealcompany.hardMathematics",
qos: .userInteractive
)
func doStuff() {
serialQueue.async {
node.getArea()
debugPrint("get area call")
}
}
}

Completion block method vs. DispatchQueue

I have implemented following completion block, one block is completed and then I update UI and object accordingly.
func doPaging() {
fetchProducts(page: pageNumber , completion: { success in
if let products = success as? Products
{
DispatchQueue.main.async {
self.products.append(contentsOf:products)
self.isWating = false;
self.productTableView.reloadData()
}
}
})
}
func fetchProducts(page: Int, completion: #escaping ((AnyObject) -> Void)) {
// URLSession call here
}
However, the following approach clearly shows restful call will happen in background thread and once it is completed, then update UI and objects.
func doPaging() {
DispatchQueue.global(qos: .background).async {
// Background Thread
fetchProducts()
DispatchQueue.main.async {
self.pageNumber += 1
self.productTableView.reloadData()
self.isWating = false
}
}
}
func fetchProducts(page: Int) {
// URLSession call here
}
I am confused between completion block method vs. DispatchQueue.
Which one is recommended?
In the first approach, you call a method fetchProducts() which internally uses NSURLSession. REST call using NSURLSession runs in background and on completion of the REST call, the completion of the task will be called. In that completion, you call your completion handler of fetchProducts(). This approach seems fine to me.
In the second approach, you use global background queue and asynchronously call NSURLSession APIs (I assume so), and don’t wait for the call to complete. The code on main queue will be instantly called and at this point the NSURLSession task may or may not have been completed.
So, this approach is problematic.
First method seems OK as long as you fetchProducts asynchronously. In fetchProducts() , if you call the completion block in the main queue you won't even need to get main queue again in the doPaging() method.
In your second method, you are calling fetchProducts() in a global (concurrent) queue. Although global queues start each task in the order they were added to queue, they run tasks concurrently. And since fechtProduct() takes time, your code block that contains self.pageNumber += 1 executed before even fetchProduct's URLSession is started. So, this approach won't work.
Completion block and Dispatch Queue are two different concepts.
Completion block is used when your function perform actions takes time to run, and need to return back and run some code even the functions has "ended". For example,
func networkCall(foo: Int, completion:#escaping (_ result:Bool)-> Void))
func otherFunc(){...}
func A(){
networkCall(foo:1){ (success) in
// handle your stuff
}
otherFunc()
}
When you run A(), it first run networkCall(), however networkCall() may takes time to run the network request and the app moved on to run otherFunc(). When the network request is done, networkCall() can call it's completion block so that A() can handle it again.
Dispatch Queue is the threading stuff safely encapsulated by Apple. Network request can be performed in Main thread as well, but it will be blocking other functions.
A common practice is to call Network request in background queue
DispatchQueue.global(qos: .background).async and call completion block after finished. If anything needs to be updated in main thread like UI, do it in the DispatchQueue.main.async

Resources