Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Abusing Async/Await (for fun and profit)

Abusing Async/Await (for fun and profit)

async/await was the hottest feature of C# 6 when it came out, bringing a radically improved way to write asynchronous code and cure us of callback hell. But how does it really work?

In this talk we will take a deeper look at the machinery behind async/await and take advantage of the lesser known tuning capabilities introduced with C# 7 to use async/await in ways you wouldn’t have thought of like re-implementing F# computation expressions and supporting mobile patterns on Android.

Jérémie Laval

October 24, 2017
Tweet

More Decks by Jérémie Laval

Other Decks in Technology

Transcript

  1. Abusing async/await
    (for fun and profit)

    View Slide

  2. Hi!
    I’m Jérémie
    @jeremie_laval
    @garuma

    View Slide

  3. Fun fact
    Mono’s Task Parallel Library (TPL) and PLinq

    View Slide

  4. Fun fact
    (2)

    View Slide

  5. Async fact sheet
    • C# 6 (circa 2015)
    • Based on TPL’s Task primitive
    • State-machine compiler trickery (like yield)
    • A fancy way to chain callbacks

    View Slide

  6. http://callbackhell.com/
    fs.readdir(source, function (err, files) {
    if (err) {
    console.log('Error finding files: ' + err)
    } else {
    files.forEach(function (filename, fileIndex) {
    console.log(filename)
    gm(source + filename).size(function (err, values) {
    if (err) {
    console.log('Error identifying file size: ' + err)
    } else {
    console.log(filename + ' : ' + values)
    aspect = (values.width / values.height)
    widths.forEach(function (width, widthIndex) {
    height = Math.round(width / aspect)
    console.log('resizing ' + filename + 'to ' + height + 'x' + height)
    this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
    if (err) console.log('Error writing file: ' + err)
    })
    }.bind(this))
    }
    })
    })
    }
    })

    View Slide

  7. The Async Trifecta
    • Awaiter/Awaitable
    • IAsyncStateMachine
    • AsyncMethodBuilder

    View Slide

  8. Awaitable/Awaiter
    • Simpler abstractions than plain Task
    • Same concept as IEnumerable/IEnumerator
    • Convention based!

    View Slide

  9. Awaitable Conventions
    interface IAwaitable
    {
    IAwaiter GetAwaiter ();
    }
    // Or
    struct StructAwaitable
    {
    IAwaiter GetAwaiter ();
    }
    // Or
    public static class Extensions
    {
    public static IAwaiter GetAwaiter (this MyClass self);
    }

    View Slide

  10. Awaiter
    interface IAwaiter : INotifyCompletion
    {
    bool IsCompleted { get; }
    void OnCompleted (Action action);
    void GetResult ();
    // Or
    OtherType GetResult ();
    }

    View Slide

  11. Async functions
    A method or anonymous function with the async modi3ier is
    called an async function. In general, the term async is used to
    describe any kind of function that has the async modi3ier.
    The return_type of an async method must be either void or a task
    type. The task types are System.Threading.Tasks.Task and types
    constructed from System.Threading.Tasks.Task
    C#6

    View Slide

  12. Async rewriting
    class MainClass

    {

    public static async Task Main (string[] args)

    {

    Console.WriteLine ("Hello");

    await Task.Delay (200);

    Console.WriteLine ("World");

    }

    }
    [AsyncStateMachine (typeof(MainClass.d__0))]

    public static Task Main (string[] args)

    {

    MainClass.d__0 d__ = new MainClass.d__0 ();

    d__.args = args;

    d__.<>t__builder = AsyncTaskMethodBuilder.Create ();

    d__.<>1__state = -1;

    AsyncTaskMethodBuilder <>t__builder = d__.<>t__builder;

    <>t__builder.Startd__0> (ref d__);

    return d__.<>t__builder.Task;

    }
    [AsyncStateMachine (typeof(AutoGeneratedStateMachine))]

    public static Task Main (string[] args)

    {

    AutoGeneratedStateMachine stateMachine = new AutoGeneratedStateMachine ();

    stateMachine.args = args;

    stateMachine.builder = AsyncTaskMethodBuilder.Create ();

    stateMachine.state = -1;

    AsyncTaskMethodBuilder builder = stateMachine.builder;

    builder.Start (ref stateMachine);

    return stateMachine.builder.Task;

    }

    View Slide

  13. Where’s my code?

    View Slide

  14. Customizing async
    • TaskCompletionSource
    • Custom awaiter/awaitable
    • Custom AsyncMethodBuilder C#7

    View Slide

  15. Custom Awaiters
    • Tasks have their own TaskAwaiter
    • Task.Yield() is a custom awaiter

    • Represent any form of async operations
    • … Or not!
    public static System.Runtime.CompilerServices.YieldAwaitable Yield ();

    View Slide

  16. The story of an optimization
    interface IAwaiter : INotifyCompletion
    {
    bool IsCompleted { get; }
    void OnCompleted (Action action);
    void GetResult ();
    // Or
    OtherType GetResult ();
    }

    View Slide

  17. Midori
    Joe Duffy
    The 3irst key optimization, therefore, is that an async
    method that doesn’t await shouldn’t allocate anything.
    We were able to share this experience with .NET in time
    for C#’s await to ship.
    http://joeduffyblog.com/2015/11/19/asynchronous-everything/

    View Slide

  18. var awaiter = /*awaitable*/.GetAwaiter ();
    if (!awaiter.IsCompleted) {
    // Indirectly call awaiter.OnCompleted
    this.builder.AwaitOnCompleted (/**/);
    return;
    }
    Allocations in there usually

    View Slide

  19. Demo: ThreadPoolAwaiter

    View Slide

  20. Customizing async
    • Custom awaiter/awaitable
    • Custom AsyncMethodBuilder C#7

    View Slide

  21. Async functions
    A method or anonymous function with the async modi3ier is
    called an async function. In general, the term async is used to
    describe any kind of function that has the async modi3ier.
    The return_type of an async method must be either void or a task
    type. The task types are System.Threading.Tasks.Task and types
    constructed from System.Threading.Tasks.Task
    C#6

    View Slide

  22. Another Optimization Story
    https://github.com/dotnet/roslyn/issues/7169

    View Slide

  23. Another Optimization Story
    https://github.com/dotnet/corefx/pull/10201

    View Slide

  24. ValueTask
    async Task Get404IntegerAsync ()
    {
    if (conditionThatsAlmostAlwaysTrue)
    return 404;
    return await Expensive404Async ();
    }
    Reference type Value type
    Allocation

    View Slide

  25. Custom AsyncMethodBuilder
    •Created to (predominantly) support ValueTask
    •Optimize fast path to remove Task allocation
    •Can be used by us too!
    C#7

    View Slide

  26. Demo: FancyTask

    View Slide

  27. ActivityTask
    • async/await totally supported by Xamarin.Android
    • Async needs to understand Android lifecycle
    • Problem 1: Android Activity survival not guaranteed!
    • Problem 2: Activity are pausable/freezable!
    https://blog.neteril.org/blog/2017/05/22/activitytask-async-await-android-cornercases/

    View Slide

  28. Demo: ActivityTask

    View Slide

  29. View Slide

  30. F# computation expression
    let divideBy bottom top = if bottom = 0 then None else Some(top/bottom)
    type MaybeBuilder() =
    member this.Bind(m, f) = Option.bind f m
    member this.Return(x) = Some x
    let maybe = new MaybeBuilder()
    let divideByWorkflow x y w z = maybe {
    let! a = x |> divideBy y
    let! b = a |> divideBy w
    let! c = b |> divideBy z
    return c
    }
    // test
    let good = divideByWorkflow 12 3 2 1
    let bad = divideByWorkflow 12 3 0 1
    https://fsharpforfunandprofit.com/series/computation-expressions.html

    View Slide

  31. Demo: F# Option
    computation expression
    https://blog.neteril.org/blog/2017/04/26/maybe-computation-expression-csharp/

    View Slide

  32. Take-away
    C# async/await is a generic facility
    to create chainable continuations
    and apply cool stuff in-between

    View Slide