[swift-evolution] async void

David Waite david at alkaline-solutions.com
Sun Nov 12 15:47:40 CST 2017


From what I understand, the C# and Swift designs are different:

- C# async functions immediately return a task which can be enqueued to complete the work (including selecting which queue for UI cases, etc). Tasks have callback behavior attached to them.

- Swift async functions immediately enqueue to do work along with a callback function to be executed once work finishes. There is no first-class representation of a Future or the like, at least by default (a callback function could be promoted to a future). There is no support for selecting which queue work or the callback runs on (although your callback could immediately enqueue work on the appropriate thread, but this does increase latency in busy systems)

Assuming I got the above all correct there is no async Task vs async Void in swift, because a function which dispatches work to a queue without taking a callback just returns void. Those functions aren’t “async” at all, you can call them normally to dispatch work just as easily as you could call them within an async block, and they would have the same behavior. They also can’t be “awaited”, because there is no callback mechanism to tell when the work completes.

Thats not to say I wouldn’t prefer the Task-type design, but it isn’t how most of the legacy asynchronous code is written to work (and would have some issues with GCD, since you can’t discover your current queue in order to enqueue more work there by default)

// Function which asynchronously dispatches work, without triggering any callback. If it throws, the error represents a synchronous issue (e.g. bad parameters or state)

func doWorkBlindly();

// Function which asynchronously dispatches work, returning a callback without data but possibly indicating an error
func doWorkExceptionally() async -> Void;

// Function which asynchronously dispatches work ,returning data and possibly an error
func doValuableWork() async -> Data;

// works fine
doWorkBlindly()
beginAsync {
    // has the same behavior as above
    doWorkBlindly()
    do {
        // can only be called within an async block
        await doWorkExceptionally()
        let result = await doValuableWork()
    }
    catch {
       fatalError()
    }
}

-DW


> On Nov 12, 2017, at 10:56 AM, Xiaodi Wu via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Sorry, I'm just getting into this conversation late and am by no means experienced in the area, but why can't the one where you *don't* want the caller to wait for the result be spelled `async -> Never`? Theoretically, `async -> Void` means you're awaiting a result with only one possible value, but if you're not waiting at all, then there is truly no result, yes?
> 
> 
> On Sun, Nov 12, 2017 at 9:27 AM, Yuta Koshizawa via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> Sorry, I had got some confusion. Please let me retry to explain.
> 
> As you said, C# provides three kinds of async functions: `async Void`,
> `async Task` and `async Task<Foo>`. All of them are necessary and
> Swift should provide same functionalities.
> 
> When we think about `async/await` in Swift, because we have already
> had `throws/try`, it is desired that `async/await` in Swift is
> consistent with `throws/try`. So it is better to have `async/await`
> without introducing a type like `Task` (or `Promise`).
> 
> Even if we employ `async/await` without `Task`, Swift has to provides
> functionalities to implement "three kinds of async functions" in C#.
> However if `async -> Void` in Swift works similarly to `async Void` in
> C#, how can we express ones like `async Task` in C#? I think there are
> two possibilities:
> 
> 1. Calling `async -> Void` functions without `await` in Swift works
> like `async Void` in C# and calling them *with* `await` works like
> `async Task` in C#.
> 2. Calling `async -> Void` functions without `await` in Swift works
> like `async Void` in C# and never support something like `async Task`
> in C#.
> 
> I think 2 is impermissible. For example, handling completion events of
> asynchronous operations without result values needs something like
> `async Task` in C#. However, with 1, we lose the benefit of static
> checks by the compiler. Because both of `fooAsync()` without `await`
> and `await fooAsync()` are allowed, even if we want it to work like
> `async Task` in C# and forget to mark `await`, the compiler tell us
> nothing and it works like `async Void` in C#. It causes unexpected
> behaviors. It is hard to fix such kinds of bugs. So I think
> introducing `beginAsync` is better.
> 
> --
> Yuta
> 
> 
> 2017-11-12 10:23 GMT+09:00 Yuta Koshizawa via swift-evolution
> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>:
> > 2017-11-12 2:57 GMT+09:00 Adam Kemp <adam.kemp at apple.com <mailto:adam.kemp at apple.com>>:
> >>
> >>
> >>> On Nov 11, 2017, at 6:24 AM, Yuta Koshizawa <koher at koherent.org <mailto:koher at koherent.org>> wrote:
> >>>
> >>> If you replace `async` with `throws`, you can get answers.
> >>>
> >>>
> >>>> Can you declare an async closure variable?
> >>>
> >>> Yes. Like `let throwingClosure:() throws -> Void = { ... }`.
> >>>
> >>>
> >>>> Can a non-async closure be passed to a function expecting a async closure?
> >>>
> >>> Yes. Like we can pass `() -> Void` to a function expecting a throwing
> >>> closure `() throws -> Void`.
> >>>
> >>> It is possible because `(Foo) throws -> Bar` is a supertype of `(Foo)
> >>> -> Bar`. `(Foo) async -> Bar` is a supertype of `(Foo) -> Bar` in the
> >>> same way.
> >>>
> >>> To treat an async function as a sync function is legal. It is similar
> >>> to make a `Promise` by `Promise(value)` which is completed
> >>> immediately.
> >>>
> >>>
> >>>> Can an async closure be passed to a function expecting a non-async closure?
> >>>
> >>> No. `() -> Void` is a subtype of `() async -> Void`. It is same as
> >>> passing `() throws -> Void` to a function expecting `() -> Void` is
> >>> not allowed.
> >>
> >> But why not? Just asserting that it must work the same as throws
> >> is not a convincing argument. You have to justify why it must work
> >> that way. I think there is good reason to allow it, which I have described.
> >> What reason is there to disallow it?
> >
> > `() async -> Void` needs to be called with `await` because it prevents
> > us from forgetting handling asynchronous operations.
> >
> > If we use callbacks to handle asynchronous operations, it is shown to
> > us by a compiler as a compilation error.
> >
> > ```
> > func fooAsync(_ handler: () -> Void) -> Void { ... }
> >
> > fooAsync() // compilation error
> >
> > fooAsync {
> >   // handles a completion event here
> > }
> > ```
> >
> > With proposed `async/await`, it is realized similarly like below.
> >
> > ```
> > func fooAsync() async -> Void { ... }
> >
> > fooAsync() // compilation error
> >
> > await fooAsync()
> > // handles a completion event here
> > ```
> >
> > However, if async void functions work like `beginAsync`, we can easily
> > forget it and it can cause unexpected behaviors.
> >
> > ```
> > func fooAsync() async -> Void { ... }
> >
> > fooAsync() // OK
> > // hard to know this line is executed asynchronously
> > ```
> >
> > Readability also suffers seriously. If we don't know `bar` in the
> > following code is a async function, it is impossible to expect lines
> > after `baz()` are executed asynchronously.
> >
> > ```
> > foo()
> > bar()
> > baz()
> > qux()
> > ```
> >
> >
> >>>> It’s weird to me that we would allow you to have async void closures but not async void functions
> >>>
> >>> I am not sure what you mean. "async void closures" and "async void
> >>> functions" have a same type. Following two are almost same.
> >>>
> >>> ```
> >>> func foo() async -> Void { ... }
> >>> let foo: () async -> Void = { ... }
> >>> ```
> >>
> >> What started this thread is my suggestion that you should be able to write
> >> an async void function. The current proposal doesn’t allow that. That’s why
> >> you have to use beginAsync.
> >>
> >> I don’t think that makes sense. It sounds like you also think that would be strange,
> >> hence your assumption that you could.
> >
> > By the reasons I wrote above, we need `await` even for async void
> > functions for checks by compilers. Then it is required to provide a
> > way to write entry points of async functions. That is `beginAsync`.
> >
> > --
> > Yuta
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> > https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <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/20171112/e35759d1/attachment.html>


More information about the swift-evolution mailing list