Implementing tasks that can be canceled in Bolts Framework (BFTask)
- As you might know,
BFTask
is an implementation of the Future and Promises construct:
"a future is a read-only placeholder view of a variable, while a promise is a writable, single assignment container which sets the value of the future".
Basically, aBFTask
is aFuture
: it is a read-only placeholder view of a variable.
ABFTaskCompletionSource
is a promise: it is a writable, single assignment container which sets the value of the future. (or an error - or cancels the task)
TheBFTask
public interface remains read-only , hence it does not allow you to cancel it directly. - This has the same answer as the previous question: BFTask is read-only and represents a read-only value. Exposing cancellation tokens would allow you to manipulate the task, which is contradictory to its nature.
- Let's take a look at it: https://github.com/BoltsFramework/Bolts-iOS/blob/master/Bolts/Common/BFCancellationToken.m
The
BFCancellationToken
token just stores a state, which theBFTask
can check. Your async task code can basically regularly checkcancellationRequested
is set to true, which allows you to manually cancel your task.
Note: The Bolts Framework iOS docs say: "A task is kind of like a JavaScript Promise" which can be confusing, because it really is a Future. I think it was just named wrong in its Javascript origins.
There is a fairly useful implementation of cancellation tokens in Bolts, but for some reason it isn't documented at all outside of the header files. The key is the usage of the BFCancellationTokenSource
. You need to keep a reference to the BFCancellationTokenSource
in order to issue and cancel a BFCancellationToken
.
In my example I have a particular function called cancellableFunction()
that issues a bunch of tasks in succession. If the function is called again before the last call has completed, I want uncompleted tasks of the previous call to be cancelled.
The key here is to pass the token
into each continueWith
function call. If at any time the token
is cancelled via the tokenSource
, unreached successBlock
s won't be executed. You can also check the status of cancellation via task.cancelled
in each BFContinuationBlock
(obviously will be false in success blocks).
Here is an example:
class ViewController: UIViewController {
...
// instance reference to tokenSource so that it can be cancelled by any function in the ViewController
var tokenSource: BFCancellationTokenSource?
...
func cancellableFunction() -> BFTask {
// First cancel the previous token
tokenSource?.cancel()
// Replace the previous TokenSource with a new one
tokenSource = BFCancellationTokenSource()
// Issue new Token from the new TokenSource
let token = tokenSource!.token
return functionThatReturnsBFTask().continueWithSuccessBlock({ (task:BFTask) -> AnyObject? in
...
return nil
}, cancellationToken: token).continueWithExecutor(BFExecutor.mainThreadExecutor(), successBlock: { (task:BFTask) -> AnyObject? in
...
return nil
}, cancellationToken: token).continueWithBlock({ (task:BFTask) -> AnyObject? in
// Here you can perform an actions you want to take on cancellation
if task.cancelled {
}
...
return nil
}, cancellationToken: token)
}
...
}
Use [self.bfTaskCancelationToken cancel];
code to cancel series BFTask
Register BFCancellationTokenSource
self.bfTaskCancelationToken = [BFCancellationTokenSource cancellationTokenSource];
[self.bfTaskCancelationToken.token registerCancellationObserverWithBlock:^{
NSLog(@"task hasbeen Cancelled.....");
//Do stuff on cancelation task
} ];
Implement Series BFTask
Note: there is cancellationToken:self.bfTaskCancelationToken.token
code after [task continueWithBlock:^id(BFTask *task)
[[[self showAlertProgressHud] continueWithBlock:^id(BFTask *taskLog) {
BFTask *task = [BFTask taskWithResult:nil];
for (int i=0; i<self.arrAssetPhotos.count; i++) {
AIAssetPhoto *assetPhoto = self.arrAssetPhotos[i];
task = [task continueWithBlock:^id(BFTask *task) {
// Return a task that will be marked as completed.
return [self processOnAssetPhoto:assetPhoto index:i completion:NULL];
} cancellationToken:self.bfTaskCancelationToken.token];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
// all asset photos process are done.
return nil;
}];
How to cancel continuous BFtask?
//just by calling one simple method
[self.bfTaskCancelationToken cancel];