[swift-evolution] [Concurrency] Fixing race conditions in async/await example

Wallacy wallacyf at gmail.com
Mon Aug 28 15:23:42 CDT 2017


And that's why I (and others) are suggesting:

func processImageData1a() async -> Image {
  let dataResource  = async loadWebResource("dataprofile.txt") // No future
type here... Just another way to call dispatch_async under the hood.
  let imageResource = async loadWebResource("imagedata.dat")

  // ... other stuff can go here to cover load latency...

  let imageTmp    = await decodeImage(dataResource, imageResource) //
Compiles force await call here...
  let imageResult = await dewarpAndCleanupImage(imageTmp)
  return imageResult
}

And now we gain all advantages of async/await again without to handle with
one more type.

Em seg, 28 de ago de 2017 às 17:07, Adam Kemp via swift-evolution <
swift-evolution at swift.org> escreveu:

> I think the biggest tradeoff is clearer when you look at the examples from
> the proposal where futures are built on top of async/await:
>
> func processImageData1a() async -> Image {
>   let dataResource  = Future { await loadWebResource("dataprofile.txt") }
>   let imageResource = Future { await loadWebResource("imagedata.dat") }
>
>   // ... other stuff can go here to cover load latency...
>
>   let imageTmp    = await decodeImage(dataResource.get(),
> imageResource.get())
>   let imageResult = await dewarpAndCleanupImage(imageTmp)
>   return imageResult
> }
>
>
> With this approach you have to wrap each call site to create a future.
> Compare to this:
>
> func processImageData1a() -> Future<Image> {
>   let dataResourceFuture  = loadWebResource("dataprofile.txt”);
>   let imageResourceFuture = loadWebResource("imagedata.dat”);
>
>   // ... other stuff can go here to cover load latency...
>
>   let imageTmp    = await decodeImage(await dataResourceFuture, await
> imageResourceFuture)
>   let imageResult = await dewarpAndCleanupImage(imageTmp)
>   return imageResult
> }
>
>
> Here, not only are the explicit wrappers gone, but this function itself
> can be used with either await or as a future. You get both options with one
> implementation.
>
> As I’ve mentioned before, C#’s implementation is not tied to any one
> particular futures implementation. The Task type is commonly used, but
> async/await does not directly depend on Task. Instead it works with any
> return type that meets certain requirements (detailed here:
> https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-anything/).
> Swift could do this using a protocol, which can be retroactively applied
> using an extension.
>
> Obviously for this to be useful we would need some kind of existing future
> implementation, but at least we wouldn’t be tied to any particular one.
> That would mean library maintainers who have already been using their own
> futures implementations could quickly adopt async/await in their code
> without having to rewrite their futures library or throw wrappers around
> every usage of async/await. They could just adopt a protocol (using an
> extension, even) and get async/await support for free.
>
> The downside is that this feature would be specific to the async/await use
> case rather than a generic coroutine implementation (i.e., there would have
> to be a separate compiler transform for yield return). It’s not clear to me
> why it should be a goal to have just one generic coroutine feature. The
> real-world usages of async/await and yield return are different enough that
> I’m not convinced we could have a single compiler feature that meets the
> needs of both cleanly.
>
> On Aug 27, 2017, at 7:35 PM, Florent Vilmart <florent at flovilmart.com>
> wrote:
>
> Adam, you’re completely right, languages as c# and JS have been through
> the path before, (callback, Promises , async/await) I believe Chris’s goal
> it to avoid building a promise implementation and go straight to a
> coroutines model, which is more deeply integrated with the compiler. I
> don’t see a particular trade off, pursuing that route, and the main benefit
> is that coroutines can power any asynchronous metaphor (Signals, Streams,
> Futures, Promises etc...) which is not true of Futures so i would tend to
> think that for the long run, and to maximize usability, async/await/yield
> would probably be the way to go.
>
> On Aug 27, 2017, 22:22 -0400, Adam Kemp <adam.kemp at apple.com>, wrote:
>
> As has been explained, futures can be built on top of async/await (or the
> other way around). You can have the best of both worlds. We are not losing
> anything by having this feature. It would be a huge improvement to have
> this as an option.
>
> However, using futures correctly requires more nested closures than you
> have shown in your examples to avoid blocking any threads. That's why
> you're not seeing the advantage to async/await. You're comparing examples
> that have very different behaviors.
>
> That said, I have also expressed my opinion that it is better to build
> async/await on top of futures rather than the other way around. I believe
> it is more powerful and cleaner to make async/await work with any arbitrary
> future type (via a protocol). The alternative (building futures on top of
> async/await) requires more code when the two are mixed. I very much prefer
> how it's done in C#, where you can freely mix the two models without having
> to resort to ad-hoc wrappers, and you can use async/await with any futures
> implementation you might already be using.
>
> I really think we should be having more discussion about the tradeoffs
> between those two approaches, and I'm concerned that some of the opinions
> about how C# does it are not based on a clear and accurate understanding of
> how it actually works in that language.
>
> --
> Adam Kemp
>
> On Aug 27, 2017, at 6:02 PM, Howard Lovatt <howard.lovatt at gmail.com>
> wrote:
>
> The async/await is very similar to the proposed Future (as I posed
> earlier) with regard to completion-handler code, they both re-write the
> imported completion-handler function using a closure, the relevant sentence
> from the Async Proposal is:
>
> "Under the hood, the compiler rewrites this code using nested closures ..."
>
>
> Unlike the proposed future code the async code is not naturally parallel,
> in the running example the following lines from the async code are run in
> series, i.e. await blocks:
>
>   let dataResource  = await loadWebResource("dataprofile.txt")
>   let imageResource = await loadWebResource("imagedata.dat")
>
> The equivalent lines using the proposed Future:
>
>   let dataResource  = loadWebResource("dataprofile.txt")
>   let imageResource = loadWebResource("imagedata.dat")
>
> Run in parallel and therefore are potentially faster assuming that
> resources, like cores and IO, are available.
>
> Therefore you would be better using a Future than an async, so why provide
> an async unless you can make a convincing argument that it allows you to
> write a better future?
>
>   -- Howard.
>
> On 28 August 2017 at 09:59, Adam Kemp <adam.kemp at apple.com> wrote:
>
>> This example still has nested closures (to create a Future), and still
>> relies on a synchronous get method that will block a thread. Async/await
>> does not require blocking any threads.
>>
>> I’m definitely a fan of futures, but this example isn’t even a good
>> example of using futures. If you’re using a synchronous get method then
>> you’re not using futures properly. They’re supposed to make it easy to
>> avoid writing blocking code. This example just does the blocking call on
>> some other thread.
>>
>> Doing it properly would show the benefits of async/await because it would
>> require more nesting and more complex error handling. By simplifying the
>> code you’ve made a comparison between proper asynchronous code (with
>> async/await) and improper asynchronous code (your example).
>>
>> That tendency to want to just block a thread to make it easier is exactly
>> why async/await is so valuable. You get simple code while still doing it
>> correctly.
>>
>> --
>> Adam Kemp
>>
>> On Aug 27, 2017, at 4:00 PM, Howard Lovatt via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> The running example used in the white paper coded using a Future is:
>>
>> func processImageData1() -> Future<Image> {
>>     return AsynchronousFuture { _ -> Image in
>>         let dataResource  = loadWebResource("dataprofile.txt") //
>> dataResource and imageResource run in parallel.
>>         let imageResource = loadWebResource("imagedata.dat")
>>         let imageTmp      = decodeImage(dataResource.get ??
>> Resource(path: "Default data resource or prompt user"), imageResource.get
>> ?? Resource(path: "Default image resource or prompt user"))
>>         let imageResult   =  dewarpAndCleanupImage(imageTmp.get ??
>> Image(dataPath: "Default image or prompt user", imagePath: "Default image
>> or prompt user"))
>>         return imageResult.get ?? Image(dataPath: "Default image or
>> prompt user", imagePath: "Default image or prompt user")
>>     }
>> }
>>
>> This also avoids the pyramid of doom; the pyramid is avoided by
>> converting continuation-handlers into either a sync or future, i.e. it is
>> the importer that eliminates the nesting by translating the code
>> automatically.
>>
>> This example using Future also demonstrates three advantages of Future:
>> they are naturally parallel (dataResource and imageResource lines run in
>> parallel), they timeout automatically (get returns nil if the Future has
>> taken too long), and if there is a failure (for any reason including
>> timeout) it provides a method of either detecting the failure or providing
>> a default (get returns nil on failure).
>>
>> There are a three of other advantages a Future has that this example
>> doesn’t show: control over which thread the Future runs on, Futures can be
>> cancelled, and debugging information is available.
>>
>> You could imagine `async` as a syntax sugar for Future, e.g. the above
>> Future example could be:
>>
>> func processImageData1() async -> Image {
>>     let dataResource  = loadWebResource("dataprofile.txt") //
>> dataResource and imageResource run in parallel.
>>     let imageResource = loadWebResource("imagedata.dat")
>>     let imageTmp      = decodeImage(dataResource.get ?? Resource(path:
>> "Default data resource or prompt user"), imageResource.get ??
>> Resource(path: "Default image resource or prompt user"))
>>     let imageResult   =  dewarpAndCleanupImage(imageTmp.get ??
>> Image(dataPath: "Default image or prompt user", imagePath: "Default image
>> or prompt user"))
>>     return imageResult.get ?? Image(dataPath: "Default image or prompt
>> user", imagePath: "Default image or prompt user")
>> }
>>
>> Since an async is sugar for Future the async runs as soon as it is
>> created (as soon as the underlying Future is created) and get returns an
>> optional (also cancel and status would be still be present). Then if you
>> want control over threads and timeout they could be arguments to async:
>>
>> func processImageData1() async(queue: DispatchQueue.main, timeout:
>> .seconds(5)) -> Image { ... }
>>
>> On Sat, 26 Aug 2017 at 11:00 pm, Florent Vilmart <florent at flovilmart.com>
>> wrote:
>>
>>> Howard, with async / await, the code is flat and you don’t have to
>>> unowned/weak self to prevent hideous cycles in the callbacks.
>>> Futures can’t do that
>>>
>>> On Aug 26, 2017, 04:37 -0400, Goffredo Marocchi via swift-evolution <
>>> swift-evolution at swift.org>, wrote:
>>>
>>> With both he now built in promises in Node8 as well as libraries like
>>> Bluebird there was ample time to evaluate them and convert/auto convert at
>>> times libraries that loved callback pyramids of doom when the flow grows
>>> complex into promise based chains. Converting to Promises seems magical for
>>> the simple case, but can quickly descend in hard to follow flows and hard
>>> to debug errors when you move to non trivial multi path scenarios. JS is
>>> now solving it with their implementation of async/await, but the point is
>>> that without the full picture any single solution would break horribly in
>>> real life scenarios.
>>>
>>> Sent from my iPhone
>>>
>>> On 26 Aug 2017, at 06:27, Howard Lovatt via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>> My argument goes like this:
>>>
>>>   1. You don't need async/await to write a powerful future type; you can
>>> use the underlying threads just as well, i.e. future with async/await is no
>>> better than future without.
>>>
>>>   2. Since future is more powerful, thread control, cancel, and timeout,
>>> people should be encouraged to use this; instead because async/await are
>>> language features they will be presumed, incorrectly, to be the best way,
>>> consequently people will get into trouble with deadlocks because they don't
>>> have control.
>>>
>>>   3. async/await will require some engineering work and will at best
>>> make a mild syntax improvement and at worst lead to deadlocks, therefore
>>> they just don't carry their weight in terms of useful additions to Swift.
>>>
>>> Therefore, save some engineering effort and just provide a future
>>> library.
>>>
>>> To turn the question round another way, in two forms:
>>>
>>>   1. What can async/wait do that a future can't?
>>>
>>>   2. How will future be improved if async/await is added?
>>>
>>>
>>>   -- Howard.
>>>
>>> On 26 August 2017 at 02:23, Joe Groff <jgroff at apple.com> wrote:
>>>
>>>>
>>>> On Aug 25, 2017, at 12:34 AM, Howard Lovatt <howard.lovatt at gmail.com>
>>>> wrote:
>>>>
>>>>  In particular a future that is cancellable is more powerful that the
>>>> proposed async/await.
>>>>
>>>>
>>>> It's not more powerful; the features are to some degree disjoint. You
>>>> can build a Future abstraction and then use async/await to sugar code that
>>>> threads computation through futures. Getting back to Jakob's example,
>>>> someone (maybe the Clang importer, maybe Apple's framework developers in an
>>>> overlay) will still need to build infrastructure on top of IBActions and
>>>> other currently ad-hoc signalling mechanisms to integrate them into a more
>>>> expressive coordination framework.
>>>>
>>>> -Joe
>>>>
>>>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>> --
>> -- Howard.
>>
>> _______________________________________________
>> 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/20170828/61489064/attachment.html>


More information about the swift-evolution mailing list