func run() {
// create a work item with the custom code
timeoutWorkItem = DispatchWorkItem {
// Insert your code here
var retryNum: Int = 0
var doRetry: Bool = true
while (doRetry) {
Thread.sleep(forTimeInterval: TimeInterval(self.mTimeoutMilliSec))
// If we are here then it means the last send did not receive a response before
// timing out. Write with no timeout or num retries so we don't spawn another
// ResponseTimeoutQueue.
retryNum += 1
if (retryNum <= self.mNumRetries) {
SessionController.sharedController.invokeWriteData(responseMsgId: self.mResponseMsgId, bytes: self.mMsgOut)
} else {
doRetry = false
}
}
// Notify the handler.
NotificationCenter.default.post(name: .timeOutMessage, object: -1)
}
//Create dispatch group
let dispatchGroup = DispatchGroup()
// execute the workItem with dispatchGroup
DispatchQueue.global().async(group: dispatchGroup, execute: timeoutWorkItem!)
//Handle code after the completion of global queue
dispatchGroup.notify(queue: DispatchQueue.global()) {
print("global queue execution completed")
}
//when the App goes to background cancel the workItem
timeoutWorkItem?.cancel()
}
What does that code exactly mean? Since I am new to swift it's hard to understand this. What I want to know is timeoutWorkItem?.cancel()
does it cancel the current thread? They call Thread.sleep sleeping on the thread so which thread will be cancelled? The code is to try to reconnect to a radar if a connection request is failed. They need to be wanted to know which request is failed, that's why they are sleeping the Thread. So if the work item cancellation causes the current thread to be cancelled? Thanks in advance.
The above function is called when we are tries to connect to the radar. I am new to swift and Stack overflow too. So please help me to find what happens when workItem cancel called?
You ask:
what is the difference between dispatch work item and dispatch group
A work item is a block of code to be dispatched to some queue. It differs from a simple closure rendition (e.g., queue.async {…}
) insofar as it supports cancelation. (More on that below.)
A dispatch group is a very different thing. It is a mechanism we use to know when a group of dispatched blocks of work is done. The typical pattern is “perform these five tasks in parallel, and let me know when they are all done, regardless of the order that they complete”.
In this case, the author is using a dispatch group in conjunction with a single dispatched item, which is very curious. There is no need for a dispatch group here. You could put that print
statement after the post
, and then the dispatch group could be excised from this code snippet.
What I want to know is timeoutWorkItem?.cancel() does it cancel the current thread?
No, it doesn’t “cancel the current thread”. (There is no such concept in GCD.)
GCD employs a “cooperative” cancelation system, in which case we must manually check whether something is canceled, and if so, simply exit the dispatched block of code. GCD does not offer “preemptive” cancelation.
So, in that context, the only thing that cancel()
does is set a Boolean indicating that item has been canceled. That’s it. Now, if a canceled work item has not yet begun execution yet, GCD will prevent it from starting at all. But if it has started, it just sets the isCancelled
flag for that work item. To support cancelation, we need to periodically check isCancelled
and exit if it is true
. But, this code doesn’t do that, so it won’t cancel once started. I suspect the author did not understand how cancelation worked in GCD.
I tried with
if timeoutWorkItem?.isCancelled == true { Thread.exit() }
but the app … crashed
When I suggested that you “exit”, I was just saying that you should exit that while loop (e.g., insert a return
or add a isCancelled
test in the while
loop).
One should never actually terminate a GCD thread. GCD threads are actually pulled from a “pool” of worker threads, and when your code is done running, it returns the thread back to the pool, ready and waiting for some subsequently dispatched item which might use that thread in the future.
The only time one would terminate a thread is if it was a Thread
object that you created yourself (which is practically unheard of nowadays, given that GCD gets us out of those weeds).
They need to be wanted to know which request is failed, that's why they are sleeping the Thread.
In this loop-sleep-retry pattern, the purpose of the “sleep” is to give it enough time for the resource to be freed again. E.g., if the resource that you are trying to access was blocked for some reason, it is likely to still be blocked 10 msec later, too, and you’ve spun, using CPU resources and more quickly hitting your retry limit, with no benefit.
Also, if one loops on a thread without a sleep, it will spin very quickly, tying up that CPU core and using a lot of energy, draining the battery, generating heat, etc. Also, we use sleep in these sorts of loops to make sure that, should the CPU have other work to do, it has a chance to switch to that until it is time to retry here again.
It must be noted that spinning in a loop is an anti-pattern, nowadays. It is inefficient (as frequently you are repeatedly trying something which is likely to fail more than once). It also ties up the thread on which it is running. (The GCD worker thread pool is quite limited.)
Sleeping on a thread is also an anti-pattern. It also ties up the worker thread. It does not support cancelation. (Not to cloud the issue, but the only “sleep” that is exempt from this rule-of-thumb is Task.sleep
in async-await patterns of Swift concurrency … that is the one “sleep” call that actually avoids blocking the thread in question and offers cancelation capabilities. But that is completely unrelated to this question.)
Now, perhaps this loop-sleep-retry is the only pattern that invokeWriteData
support supports, in which case you have to do what you have to do. (We cannot comment without seeing what invokeWriteData
is doing, but that is probably beyond the scope of this question, anyway.)
But let us say that invokeWriteData
is writing to some shared resource that does not support concurrent writes. In that case, a contemporary pattern might be to have a custom GCD serial queue for this write operation. Then, anything that needs to be written should just be added to that serial queue. Rather than needing retry logic, GCD won’t try writing until the prior write was done, but will write as soon as the prior write (if any) was done. No more while
loop. No more praying that you picked a sleep duration of the appropriate length. No more inefficiencies resulting from spinning, sleeping too long, not sleeping long enough, etc.
Needless to say, while this seems to beg for a serial queue, we cannot be sure unless we know what sort of resource contention this “write” operation is trying to address. If it is contention solely within the app, then it is screaming for a serial queue implementation. But we simply cannot say on the basis of what has been provided thus far. (And diving into this should probably be a separate question, not discussed further here.)
Finally, you suggested that the app is canceling the task when it goes to the background. You might consider background tasks, effectively asking the OS for a little time to finish these writes before it suspends the app.
Also, if you have not already, you might also want to make sure that it is performing these writes atomically in case the app is terminated in the middle of the write.