How can I invalidate this DispatchWorkItem in Swift? [duplicate] - ios

I am currently playing around with Grand Central Dispatch and discovered a class called DispatchWorkItem. The documentation seems a little incomplete so I am not sure about using it the right way. I created the following snippet and expected something different. I expected that the item will be cancelled after calling cancel on it. But the iteration continues for some reason. Any ideas what I am doing wrong? The code seems fine for me.
#IBAction func testDispatchItems() {
let queue = DispatchQueue.global(attributes:.qosUserInitiated)
let item = DispatchWorkItem { [weak self] in
for i in 0...10000000 {
print(i)
self?.heavyWork()
}
}
queue.async(execute: item)
queue.after(walltime: .now() + 2) {
item.cancel()
}
}

GCD does not perform preemptive cancelations. So, to stop a work item that has already started, you have to test for cancelations yourself. In Swift, cancel the DispatchWorkItem. In Objective-C, call dispatch_block_cancel on the block you created with dispatch_block_create. You can then test to see if was canceled or not with isCancelled in Swift (known as dispatch_block_testcancel in Objective-C).
func testDispatchItems() {
let queue = DispatchQueue.global()
var item: DispatchWorkItem?
// create work item
item = DispatchWorkItem { [weak self] in
for i in 0 ... 10_000_000 {
if item?.isCancelled ?? true { break }
print(i)
self?.heavyWork()
}
item = nil // resolve strong reference cycle of the `DispatchWorkItem`
}
// start it
queue.async(execute: item!)
// after five seconds, stop it if it hasn't already
queue.asyncAfter(deadline: .now() + 5) {
item?.cancel()
item = nil
}
}
Or, in Objective-C:
- (void)testDispatchItem {
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
static dispatch_block_t block = nil; // either static or property
__weak typeof(self) weakSelf = self;
block = dispatch_block_create(0, ^{
for (long i = 0; i < 10000000; i++) {
if (dispatch_block_testcancel(block)) { break; }
NSLog(#"%ld", i);
[weakSelf heavyWork];
}
block = nil;
});
// start it
dispatch_async(queue, block);
// after five seconds, stop it if it hasn't already
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (block) { dispatch_block_cancel(block); }
});
}

There is no asynchronous API where calling a "Cancel" method will cancel a running operation. In every single case, a "Cancel" method will do something so the operation can find out whether it is cancelled, and the operation must check this from time to time and then stop doing more work by itself.
I don't know the API in question, but typically it would be something like
for i in 0...10000000 {
if (self?.cancelled)
break;
print(i)
self?.heavyWork()
}

DispatchWorkItem without DispatchQueue
let workItem = DispatchWorkItem{
//write youre code here
}
workItem.cancel()// For Stop
DispatchWorkItem with DispatchQueue
let workItem = DispatchWorkItem{
//write youre code here
}
DispatchQueue.main.async(execute: workItem)
workItem.cancel()// For Stop
Execute
workItem.perform()// For Execute
workItem.wait()// For Delay Execute

Related

How to break out of a loop that uses async DispatchQueue inside

I am using a for loop coupled with a DispatchQueue with async to incrementally increase playback volume over the course of a 5 or 10-minute duration.
How I am currently implementing it is:
for i in (0...(numberOfSecondsToFadeOut*timesChangePerSecond)) {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)/Double(timesChangePerSecond)) {
if self.activityHasEnded {
NSLog("Activity has ended") //This will keep on printing
} else {
let volumeSetTo = originalVolume - (reductionAmount)*Float(i)
self.setVolume(volumeSetTo)
}
}
if self.activityHasEnded {
break
}
}
My goal is to have activityHasEnded to act as the breaker. The issue as noted in the comment is that despite using break, the NSLog will keep on printing over every period. What would be the better way to fully break out of this for loop that uses DispatchQueue.main.asyncAfter?
Updated: As noted by Rob, it makes more sense to use a Timer. Here is what I did:
self.fadeOutTimer = Timer.scheduledTimer(withTimeInterval: timerFrequency, repeats: true) { (timer) in
let currentVolume = self.getCurrentVolume()
if currentVolume > destinationVolume {
let volumeSetTo = currentVolume - reductionAmount
self.setVolume(volumeSetTo)
print ("Lowered volume to \(volumeSetTo)")
}
}
When the timer is no longer needed, I call self.fadeOutTimer?.invalidate()
You don’t want to use asyncAfter: While you could use DispatchWorkItem rendition (which is cancelable), you will end up with a mess trying to keep track of all of the individual work items. Worse, a series of individually dispatch items are going to be subject to “timer coalescing”, where latter tasks will start to clump together, no longer firing off at the desired interval.
The simple solution is to use a repeating Timer, which avoids coalescing and is easily invalidated when you want to stop it.
You can utilise DispatchWorkItem, which can be dispatch to a DispatchQueue asynchronously and can also be cancelled even after it was dispatched.
for i in (0...(numberOfSecondsToFadeOut*timesChangePerSecond)) {
let work = DispatchWorkItem {
if self.activityHasEnded {
NSLog("Activity has ended") //This will keep on printing
} else {
let volumeSetTo = originalVolume - (reductionAmount)*Float(i)
self.setVolume(volumeSetTo)
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)/Double(timesChangePerSecond), execute: work)
if self.activityHasEnded {
work.cancel() // cancel the async work
break // exit the loop
}
}

Dispatch Queue inside of Dispatch.main.async

I am currently working on a project that requires me to update a database, and then loop over the database to compare some values. Because of the database update time I have decided to use a delayed call, to give it time to update. Here is the structure of my calls:
//Give database time to update
DispatchQueue.main.asyncAfter(deadline: .now() +5) {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
closure {
...data processing....
dispatchGroup.leave()
}
//wait for data-processing inside closure to complete
dispatchGroup.wait()
}
This freezes my app. It was my understanding that the closure should run asynchronously and so I was hoping the enter/leave balance would be reached while at dispatchGroup.wait(). Any help for solving this problem would be greatly appreciated thank you!
Note also I occasionally get an EXC_BREAKPOINT error at the line I have indicated, in the following function:
- (void) fireEvent:(id <FEvent>)event queue:(dispatch_queue_t)queue {
if ([event isCancelEvent]) {
FCancelEvent *cancelEvent = event;
FFLog(#"I-RDB065001", #"Raising cancel value event on %#", event.path);
NSAssert(self.cancelCallback != nil, #"Raising a cancel event on a listener with no cancel callback");
dispatch_async(queue, ^{
self.cancelCallback(cancelEvent.error);
});
} else if (self.callback != nil) {
FDataEvent *dataEvent = event;
FFLog(#"I-RDB065002", #"Raising value event on %#", dataEvent.snapshot.key);
dispatch_async(queue, ^{
self.callback(dataEvent.snapshot); <---------
});
}
Dispatch is asynchronous but you are running it on the main thread.
You can simply run it on the background thread, it won't bloack the UI.
DispatchQueue.global(qos: .background).async {
}

Updating UIProgressView Progress from background thread

I'm using a UIProgressView and it uses the observedProgress property. I then have a variable of type Progress that is observed.
Now I'm writing to Core Data on a background thread and then updating the completedUnitCount but it's crashing.
Here's the code:
var downloadProgress: Progress
init() {
downloadProgress = Progress()
}
func saveStuff() {
let stuff: [[String: Any]] = //some array of dictionaries
downloadProgress.totalUnitCount = Int64(stuff.count)
persistentContainer.performBackgroundTask { (context) in
for (index, item) in stuff.enumerated() {
// create items to be saved
context.perform {
do {
try context.save()
self.downloadProgress.completedUnitCont = Int64(index + 1)
} catch {
// handle error
}
}
}
}
}
So it's crashing on line self.downloadProgress.completedUnitCont = Int64(index + 1). I realise in writing this that I should also be using weak or unowned self to stop retain cycles, but are there other issues?
All the UI related code have to be performed from the main thread, so you have to dispatch call self.downloadProgress.completedUnitCont = Int64(index + 1) to main thread. Something like this:
DispatchQueue.main.async {
self.downloadProgress.completedUnitCont = Int64(index + 1)
}
Apple Says:
Updating UI on a thread other than the main thread is a common mistake that can result in missed UI updates, visual defects, data corruptions, and crashes.
So whenever you are performing any task on background thread and need to make any ui updates in the process, use all those code inside the following block.
DispatchQueue.main.async{ <uiupdate code here> }

How to wait first dispatch to finish execution

I have 2 dispatch_async() like this :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
/* Code here */
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
/* Code here */
}
I want the second dispatch to wait until the first one finish his execution. how i can do that ?
Thank's in advance
I'll give a few solutions, in the order of increasing complexity:
1
The simplest way is to include both code blocks in the same async calls:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// code block 1
// code block 2
}
2
If you don't know precisely when they will run, for example, code block 1 is triggered when user presses a button and code block 2 is run when user presses another button, use a serial queue:
let serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL)
dispatch_async(serialQueue) {
// code block 1
}
dispatch_async(serialQueue) {
// code block 2
}
3
If your code blocks run asynchronously, like first making a webservice call to authenticate, then making a second call to get the user's profile, you have to implement waiting:
let groupID = dispatch_group_create()
let task1 = session.dataTaskWithRequest(request1) { data, response, error in
// handle the response...
// Tell Grand Central Dispatch that the request is done
dispatch_group_leave(groupID)
}
let task2 = session.dataTaskWithRequest(request2) { data, response, error in
// handle the response...
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
dispatch_group_enter(groupID) // Tell GCD task1 is starting
task1.resume()
dispatch_group_wait(groupID, DISPATCH_TIME_FOREVER) // Wait until task1 is done
task2.resume()
}
4
For anything more complicated, I strongly suggest you learn NSOpereationQueue. There's a WWDC session on it

Waiting for async function to finish using GCD

I have an async function that queries Parse. I need to wait until all objects from the Parse query have returned before calling my second function. The problem is, I'm using:
var group: dispatch_group_t = dispatch_group_create()
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) { () -> Void in
asyncFunctionA() // this includes an async Parse query
}
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) { () -> Void in
asyncFunctionB() // must be called when asyncFunctionA() has finished
}
...but, asyncFunctionB() is getting called before I even have any objects appended to my arrays in asyncFunctionA(). Isn't the point of using GCD notify to observe the completion of a prior function? Why isn't that working here?
Just like Parse employs the concept of completion block/closures, you need to do the same in your asyncFunctionA:
func asyncFunctionA(completionHandler: () -> ()) {
// your code to prepare the background request goes here, but the
// key is that in the background task's closure, you add a call to
// your `completionHandler` that we defined above, e.g.:
gameScore.saveInBackgroundWithBlock { success, error in
if (success) {
// The object has been saved.
} else {
// There was a problem, check error.description
}
completionHandler()
}
}
Then you could do something like your code snippet:
let group = dispatch_group_create()
dispatch_group_enter(group)
asyncFunctionA() {
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
self.asyncFunctionB()
}
Note, if function A was really using Parse's asynchronous methods, then there's no need to use dispatch_async there. But if you need it for some reason, feel free to add that back in, but make sure the dispatch_group_enter occurs before to dispatch to some background thread.
Frankly, I'd only use groups if I had a whole bunch of items added to this group. If it really was just B waiting for single call to A, I'd retire the groups entirely and just do:
asyncFunctionA() {
self.asyncFunctionB()
}

Resources