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

Wallacy wallacyf at gmail.com
Tue Aug 29 11:22:14 CDT 2017


I dont like the idea of beginAsync, suspendAsync be a standard library
functions to support a language level resource.

beginAsync can be only `async` and suspendAsync  can be "await resync"
(something like that).

Saing that, cancel a operations is not that simple! Abrupt abort any thread
can leave some consequences. Usually, for network code is ok, but some
other multi-thread operations is not (like calculus, drawing, file
operations, etc)

Timeout is not useful on mathematical operations, abort any thread which is
accessing a physical device also can be dangerous (I broke one machine one
time). There's no straight answer to "how" we can stop a execution block.
Pause/Cancel when using a GCD queue is not that simple too! We need to
remember that one of Swift's goals is to be a systems language. Not
everything that is useful in "high level" is useful in "low level".

Yes, coordination is important, but not a simple question to a run-time
agnostic way to implement concurrency. Of course we can choose to make a
run-time specific way to implement this, but i don't think is a valuable
choice now.

But.... return something beginAsync, suspendAsync, ever a `bool` is a good
ideia (the proposal cover that too).

Em seg, 28 de ago de 2017 às 22:42, Howard Lovatt via swift-evolution <
swift-evolution at swift.org> escreveu:

> I think this makes the proposed async/await coroutines more viable.
> Building on this, if the proposed Cancelable became:
>
>     protocol ExecutionControl {
>         /// Causes the executing coroutine to throw
> `TerminateCoroutine.cancelled` and also terminates all the sub-coroutines.
>         var isCancelled: Bool { set }
>
>         /// If an await exceeds timeout then executing task throws
> `TerminateCoroutine.timeout` and also terminates all the sub-coroutines.
>         var timeout: DispatchTimeInterval { get set }
>     }
>
> Then async/await coroutine would have feature parity with a typical
> `Future` - which would be good.
>
> PS Effectively the execution service is returning the `Future`!
>
>   -- Howard.
>
> On 29 August 2017 at 09:42, Marc Schlichte via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>>
>> 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
>>
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
> _______________________________________________
> 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/8629c28b/attachment.html>


More information about the swift-evolution mailing list