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.

167f2c9daf3e040134359926747a510b?s=128

Jérémie Laval

October 24, 2017
Tweet

Transcript

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

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

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

  4. Fun fact (2)

  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
  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)) } }) }) } })
  7. The Async Trifecta • Awaiter/Awaitable • IAsyncStateMachine • AsyncMethodBuilder

  8. Awaitable/Awaiter • Simpler abstractions than plain Task • Same concept

    as IEnumerable/IEnumerator • Convention based!
  9. Awaitable Conventions interface IAwaitable { IAwaiter GetAwaiter (); } //

    Or struct StructAwaitable { IAwaiter GetAwaiter (); } // Or public static class Extensions { public static IAwaiter GetAwaiter (this MyClass self); }
  10. Awaiter interface IAwaiter : INotifyCompletion { bool IsCompleted { get;

    } void OnCompleted (Action action); void GetResult (); // Or OtherType GetResult (); }
  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<T> C#6
  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.<Main>d__0))]
 public static Task Main (string[] args)
 {
 MainClass.<Main>d__0 <Main>d__ = new MainClass.<Main>d__0 ();
 <Main>d__.args = args;
 <Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create ();
 <Main>d__.<>1__state = -1;
 AsyncTaskMethodBuilder <>t__builder = <Main>d__.<>t__builder;
 <>t__builder.Start<MainClass.<Main>d__0> (ref <Main>d__);
 return <Main>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<AutoGeneratedStateMachine> (ref stateMachine);
 return stateMachine.builder.Task;
 }
  13. Where’s my code?

  14. Customizing async • TaskCompletionSource • Custom awaiter/awaitable • Custom AsyncMethodBuilder

    C#7
  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 ();
  16. The story of an optimization interface IAwaiter : INotifyCompletion {

    bool IsCompleted { get; } void OnCompleted (Action action); void GetResult (); // Or OtherType GetResult (); }
  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/
  18. var awaiter = /*awaitable*/.GetAwaiter (); if (!awaiter.IsCompleted) { // Indirectly

    call awaiter.OnCompleted this.builder.AwaitOnCompleted (/**/); return; } Allocations in there usually
  19. Demo: ThreadPoolAwaiter

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

  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<T> C#6
  22. Another Optimization Story https://github.com/dotnet/roslyn/issues/7169

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

  24. ValueTask async Task<int> Get404IntegerAsync () { if (conditionThatsAlmostAlwaysTrue) return 404;

    return await Expensive404Async (); } Reference type Value type Allocation
  25. Custom AsyncMethodBuilder •Created to (predominantly) support ValueTask •Optimize fast path

    to remove Task allocation •Can be used by us too! C#7
  26. Demo: FancyTask

  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/
  28. Demo: ActivityTask

  29. None
  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
  31. Demo: F# Option computation expression https://blog.neteril.org/blog/2017/04/26/maybe-computation-expression-csharp/

  32. Take-away C# async/await is a generic facility to create chainable

    continuations and apply cool stuff in-between