[swift-evolution] [Concurrency] modifying beginAsync, suspendAsync to support cancellation

Marc Schlichte marc.schlichte at googlemail.com
Mon Aug 28 18:42:32 CDT 2017


> Am 19.08.2017 um 20:33 schrieb Marc Schlichte via swift-evolution <swift-evolution at swift.org>:
> 
> Hi,
> 
> to support cancellation, I propose the following changes to `beginAsync()` and `suspendAsync()`:
> 
> `beginAsync()` returns an object adhering to a `Cancelable` protocol:
> 
> ```
> func beginAsync(_ body: () async throws-> Void) rethrows -> Cancelable
> 
> protocol Cancelable { func cancel() }
> ```
> 
> `suspendAsync()` takes a new thunk parameter:
> 
> ```
> func suspendAsync<T>(onCancel: () -> Void, body: (cont: (T) -> Void, err: (Error) -> Void) async -> T 
> ```
> 
> Now, when `cancel()` is invoked, the `onCancel` thunk in the current suspension (if any) will be called.
> 
> 
> Example:
> 
> ```
> var task: Cancelable?
> 
> @IBAction func buttonDidClick(sender: AnyObject) {
>  task = beginAsync {
>    do {
>      let image = try await processImage()
>      imageView.image = image 
>    } catch AsyncError.canceled {
>      imageView.image = nil // or some fallback image...
>    } catch {
>      // other handling
>    }
>  }  
> )
> 
> @IBAction func cancelDidClick(sender: AnyObject) {
>  task?.cancel()
> }

Just adding here that instead of directly using the low-level `beginAsync`, a Future/Promise could be used instead:

```
var task: Future<UIImage>?

@IBAction func buttonDidClick(sender: AnyObject) {
  task = Future {
    try await processImage()
  }
  do {
    imageView.image = try await task!.get()
  } catch AsyncError.canceled {
    imageView.image = nil // or some fallback image...
  } catch {
    // other handling
  }
}

@iBAction func cancelDidClick(sender: AnyObject) {
  task?.cancel()
}
```

Of course, the init of Future would have to be changed

  convenience init(_ body: () throws async -> T) {
    self.init()
    task = beginAsync {
      do {
        self.fulfill(try await body())
      } catch {
        self.fail(error)
      }
    }
  }
(BTW also added missing throws and try in code above)

and `cancel()` would have to be added to `Future`:

```
public func cancel() {
  task?.cancel()
}

```

> 
> func processImage() async throws -> UIImage {
>  // This processing should be on a background queue (or better an Actor :-) - but ignored for this example
>  var cancelled = false
>  suspendAsync(onCancel: {

>    cancelled = true
>  }, body: { cont, err in
>     while !done && !cancelled {
>       // do the processing on image until done or canceled
>     }
>     guard !cancelled else { err(AsyncError.canceled) } // BTW, maybe change signature of `suspendAsync` to allow to throw here instead
>     cont(image)
>  }
> }
> ```

^ BTW, this should be `return  await suspendAsync(…`
> 
> Cheers
> Marc
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170829/388422b2/attachment.html>


More information about the swift-evolution mailing list