Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing tasks that can be canceled in Bolts Framework (BFTask)

BFTask has been good to me but I have one complaint: I've yet to see a working example of how you ought to cancel a task. The entirety of the documentation on the subject is found on their GitHub page with a single lowly section that includes everything but the part I care about: how to cancel the task.

// Somewhere else.
MYCancellationToken *cancellationToken = [[MYCancellationToken alloc] init];
[obj doSomethingComplicatedAsync:cancellationToken];

// When you get bored...
[cancellationToken cancel];

Their code snippet is followed by:

Note: The cancellation token implementation should be thread-safe.

I am wondering the following:

  1. Is there a good reason that they may not have provided the cancel method on the BFTask interface itself? They have a property representing whether the task was canceled but no means to cancel it.
  2. Is there a good reason that they would not include a cancellationToken(s) property on the BFTask itself?
  3. Is the implementation of cancel strongly coupled to the task itself? Or is a general implementation possible as in the case of cancelAllOperations of an NSOperationQueue?
like image 390
tacos_tacos_tacos Avatar asked Jun 12 '15 07:06

tacos_tacos_tacos


2 Answers

  1. 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, a BFTask is a Future: it is a read-only placeholder view of a variable.
    A BFTaskCompletionSource is a promise: it is a writable, single assignment container which sets the value of the future. (or an error - or cancels the task)
    The BFTask public interface remains read-only , hence it does not allow you to cancel it directly.
  2. 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.
  3. 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 the BFTask can check. Your async task code can basically regularly check cancellationRequested 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.
like image 91
Byte Welder Avatar answered Sep 23 '22 11:09

Byte Welder


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 successBlocks 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)
   }

   ...

}
like image 39
Harel Avatar answered Sep 23 '22 11:09

Harel