DotNetDay18: Refactor your codebase to async/await by Daniel Marbach

DotNetDay18: Refactor your codebase to async/await by Daniel Marbach

The die is cast. Your business- and infrastructure logic will need to move gradually to asynchronous code. Changing large code-bases to async/await seems like a monumental effort. It doesn't have to be! In this talk, I show you a four-phased approach to evolving your code-base towards async/wait. In the identification phase, we classify the components which would benefit from async/await. In the exploration phase, we discover potential road blockers which might hinder the async/await adoption.

In the obstacle phase, we learn to redesign parts of the code to remove the previously identified road blockers. In the bring-it-together phase, we gradually move the components which benefit the most from async/await to a full asynchronous API. Small steps. No Big Bang. Join me on a journey towards async/await and learn to leverage the power of asynchronous code to embrace the cloud-first APIs of today and tomorrow.

Original Source:
https://github.com/danielmarbach/RearchitectTowardsAsyncAwait

Do you like beer? Check out Daniels side project:
www.beergeeks.ch

E6cffbf3b7a5fbfee4707033ef1636f5?s=128

dotnetday

May 29, 2018
Tweet

Transcript

  1. Rearchitect your code Welcome towards async/await

  2. Software Engineer Enthusiastic Software Engineer Microsoft MVP @danielmarbach particular.net/blog planetgeek.ch

  3. None
  4. Goals Why async is the future How to gradually move

    your code towards async / await The toolbelt for an async ninja target
  5. Premise

  6. Intro Phases WrapUp

  7. Intro Phases WrapUp

  8. The die is

  9. javascript async function chainAnimationsPromise(elem, animations) { let ret = null;

    try { for(const anim of animations) { ret = await anim(elem); } catch(e) { /* ignore and keep going */ } return ret; } ES2017 $ npm install babel-plugin-syntax-async-functions $ npm install babel-plugin-transform-async-to-generator
  10. httpclient using (var client= new HttpClient()) { var response= await

    client.GetAsync("api/products/1"); if (response.IsSuccessStatusCode) { var product = await response.Content.ReadAsAsync<Product>(); } }
  11. Azure SDK var queryable = client.CreateDocumentQuery<Entity>(...) .AsDocumentQuery(); while (queryable.HasMoreResults) {

    foreach(var e in await queryable.ExecuteNextAsync<Entity>()) { // Iterate through entities } }
  12. event-driven async

  13. Task uniform

  14. Task IO-bound

  15. Task CPU-bound

  16. Recap Use async Task instead of async void best-practices

  17. Recap Use async Task instead of async void Async all

    the way, don’t mix blocking and asynchronous code best-practices
  18. Async / await is

  19. None
  20. It kicks your servers

  21. IO pool Worker pool Task.Run Task.Factory.StartNew Parallel.For Parallel.ForEach await iobound

    iobound.FireForget()
  22. IO pool Worker pool Task.Run Task.Factory.StartNew Parallel.For Parallel.ForEach await iobound

    iobound.FireForget()
  23. Azure Service Bus 26 times NServiceBus Azure Storage Queues 6

    times MSMQ 3 times more message throughput https://github.com/Particular/EndToEnd/tree/master/src/PerformanceTests https://particular.net/blog/rabbitmq-updates-in-nservicebus-6 RabbitMQ 5 times
  24. None
  25. bring together identify Ex Plore overcome

  26. Intro Phases WrapUp

  27. bring together identify Ex Plore overcome

  28. Identify IO-bound

  29. NServiceBus IO-bound Configuration Scanning Pipeline Transport Serialization Persistence … …

  30. bring together identify Ex Plore overcome

  31. Explore IO-bound High-level Spike Low-level Spike

  32. RabbitMQ Client IO-bound High-level Spike ConnectionFactory Connection Model Consumers github.com/rabbitmq/rabbitmq-dotnet-client/pull/151

  33. Low-level Spike RabbitMQ Client IO-bound Protocol MessageBuilder NetworkReader/Writer Content /

    Headers / Frames github.com/rabbitmq/rabbitmq-dotnet-client/pull/149
  34. watch! Event handlers Locks Monitor Semaphore / Mutex Auto /

    ManualResetEvent Ref/Out parameters Thread Ambient state IO-bound calls in 3rd Party libs Remote Procedure Calls
  35. Event handler

  36. watch! public delegate void EventHandler(object sender, EventArgs e); public delegate

    void EventHandler<TEventArgs>(object sender, TEventArgse); async void MyEventHandler(object sender, EventArgs e) { await Task.Yield(); throw new InvalidOperationException(); } Event handlers
  37. ManualResetEvent

  38. var syncEvent = new ManualResetEvent(false); var t1 = Task.Run(() =>

    { syncEvent.WaitOne(); }); var t2 = Task.Run(() => { Thread.Sleep(2000); syncEvent.Set(); }); await Task.WhenAll(t1, t2); ManualResetEvent watch!
  39. void stinks wait smells Async all the way means avoid

    blocking code Remember
  40. Locks

  41. http://stackoverflow.com/questions/7612602/why-cant-i- use-the-await-operator-within-the-body-of-a-lock-statement var locker = new object(); lock (locker) {

    await Task.Yield(); } Error CS1996 Cannot await in the body of a lock statement locks watch!
  42. Ref / Out parameters

  43. static async Task Out(string content, out string parameter) { var

    randomFileName = Path.GetTempFileName(); using (var writer = new StreamWriter(randomFileName)) { await writer.WriteLineAsync(content); } parameter = randomFileName; } Ref/Out Error CS1988 Async methods cannot have ref or out parameters watch!
  44. Ambient state

  45. class ClassWithAmbientState { static ThreadLocal<int> ambientState = new ThreadLocal<int>(() =>

    1); public void Do() { ambientState.Value++; } } Ambient state watch!
  46. var instance = new ClassWithAmbientState(); var tasks = new Task[3];

    for (int i = 0; i < 3; i++) { tasks[i] = Task.Run(() => { instance.Do(); Thread.Sleep(200); instance.Do(); }); } await Task.WhenAll(tasks); Ambient state watch!
  47. Older constructs bound to threads fall apart in the async/await

    world Remember
  48. Forget thread! think

  49. bring together identify Ex Plore overcome

  50. Fix it! Event handlers Locks Monitor Semaphore / Mutex Auto

    / ManualResetEvent Ref/Out parameters Thread Ambient state IO-bound calls in 3rd Party libs Remote Procedure Calls
  51. Event handler

  52. public delegate void EventHandler(object sender, EventArgs e); public delegate void

    EventHandler<TEventArgs>(object sender, TEventArgse); async void MyEventHandler(object sender, EventArgs e) { await Task.Yield(); throw new InvalidOperationException(); }
  53. Fix it! public delegate Task AsyncEventHandler(object sender, EventArgs e); Event

    handlers async Task MyAsyncEventHandler(object sender, EventArgs e) { } async Task MyEventHandler(object sender, EventArgs e) { await Task.Yield(); throw new InvalidOperationException(); }
  54. Fix it! protected virtual Task OnMyAsyncEvent() { var invocations =

    handler.GetInvocationList(); var handlerTasks = new Task[invocationList.Length]; for (int i = 0; i < invocations.Length; i++) { handlerTasks[i] = ((AsyncEventHandler)invocations[i])(...); } return Task.WhenAll(handlerTasks); } Event handlers
  55. ManualResetEvent

  56. var syncEvent = new ManualResetEvent(false); var t1 = Task.Run(() =>

    { syncEvent.WaitOne(); }); var t2 = Task.Run(() => { Thread.Sleep(2000); syncEvent.Set(); }); await Task.WhenAll(t1, t2);
  57. var tcs = new TaskCompletionSource<object>(); var t1 = ((Func<Task>)(async ()

    => { await tcs.Task; }); var t2 = ((Func<Task>)(async () => { await Task.Delay(2000); tcs.TrySetResult(null); }); await Task.WhenAll(t1, t2); ManualResetEvent Fix it!
  58. TaskCompletionSource belongs into your toolbelt Remember

  59. ManualResetEvent Works for set once events only. For async reset

    events, an approach is available on github Fix it! https://github.com/danielmarbach/RearchitectTowardsAsyncAwait /blob/master/presentation/AsyncManualResetEvent.cs
  60. Locks

  61. locks Can we change the code so that we don’t

    have to await inside the lock? Fix it!
  62. var locker = new object(); lock (locker) { await Task.Yield();

    } Error CS1996 Cannot await in the body of a lock statement
  63. int sharedResource = 0; var semaphore = new SemaphoreSlim(1); var

    tasks = new Task[3]; for (int i = 0; i < 3; i++) { tasks[i] = ((Func<Task>) (async () => { await semaphore.WaitAsync(); sharedResource++; semaphore.Release(); }}))(); } await Task.WhenAll(tasks); locks Fix it!
  64. SemaphoreSlim belongs into your toolbelt Remember

  65. locks using (await semaphore.LockAsync()) { sharedRessource++; } Fix it! https://github.com/danielmarbach/RearchitectTowardsAsyncAwait

    /blob/master/presentation/AsyncLock.cs
  66. Ref / Out parameters

  67. static async Task Out(string content, out string parameter) { var

    randomFileName = Path.GetTempFileName(); using (var writer = new StreamWriter(randomFileName)) { await writer.WriteLineAsync(content); } parameter = randomFileName; } Error CS1988 Async methods cannot have ref or out parameters
  68. static async Task<string> Out(string content) { var randomFileName = Path.GetTempFileName();

    using (var writer = new StreamWriter(randomFileName)) { await writer.WriteLineAsync(content); } return randomFileName; } Ref/Out Fix it!
  69. Ambient state

  70. class ClassWithAmbientState { static ThreadLocal<int> ambientState = new ThreadLocal<int>(() =>

    1); public void Do() { ambientState.Value++; } }
  71. class ClassWithAmbientState{ static AsyncLocal<int> ambientState= new AsyncLocal<int>(); static ClassWithAmbientState() {

    ambientState.Value= 1; } public void Do() { ambientState.Value++; } } Ambient state Fix it!
  72. var instance = new ClassWithAmbientState(); var tasks = new Task[3];

    for (int i = 0; i < 3; i++) { tasks[i] = ((Func<Task>)(async () => { instance.Do(); await Task.Delay(200).ConfigureAwait(false); instance.Do(); }))(); } await Task.WhenAll(tasks); Ambient state Fix it!
  73. Ambient state Fix it! Even better: Can we change the

    code so that we float state into methods that need it?
  74. class ClassWithAmbientState{ public int Do(int current) { current++; return current;

    } } Ambient state Fix it!
  75. var instance = new ClassWithFloatingState(); var tasks = new Task[3];

    for (int i = 0; i < 3; i++) { tasks[i] = ((Func<Task>)(async() => { int current = 1; current = instance.Do(current); await Task.Delay(200).ConfigureAwait(false); instance.Do(current); }))(); } await Task.WhenAll(tasks); Ambient state Fix it!
  76. bring together identify Ex Plore overcome

  77. High-level Low-level Bring it together

  78. Bring it together void HighLevel() { try { MidLevel(); }

    catch(InvalidOperationException) { } } void MidLevel() { … LowLevel(); … } void LowLevel() { }
  79. Bring it together void HighLevel() { try { MidLevel(); }

    catch(InvalidOperationException) { } } void MidLevel() { … LowLevel().GetAwaiter().GetResult(); … } async Task LowLevel() { }
  80. Commit. Push.

  81. Bring it together void HighLevel() { try { MidLevel().GetAwaiter().GetResult(); }

    catch(InvalidOperationException) { } } async Task MidLevel() { … await LowLevel().ConfigureAwait(false); … } async Task LowLevel() { }
  82. Commit. Push.

  83. Bring it together async Task HighLevel() { try { await

    MidLevel ().ConfigureAwait(false); } catch(InvalidOperationException) { } } async Task MidLevel() { … await LowLevel().ConfigureAwait(false); … } async Task LowLevel() { }
  84. Yehaa! Async all the way

  85. None
  86. Reality

  87. Reality void Entry() { IOBound(); CPUBound(); CPUBound(); IOBound(); } async

    Task Entry() { await IOBound(); CPUBound(); CPUBound(); await IOBound(); }
  88. async Task Entry() { await IOBound(); HeavyCPUBound(); HeavyCPUBound(); await IOBound();

    } Reality async Task Entry() { await IOBound(); await Task.Run(() => { HeavyCPUBound(); HeavyCPUBound(); }); await IOBound(); } async Task Entry() { await IOBound(); await IOBound(); await Task.Run(() => { HeavyCPUBound(); HeavyCPUBound(); }); }
  89. Intro Phases WrapUp

  90. Recap Use iPob to move your code step by step

    towards async / await IO-bound paths benefit from async Uniform API of Task allows to await CPU-bound as well as IO-bound tasks reminder
  91. Thanks

  92. github.com/danielmarbach/RearchitectTowardsAsyncAwait Slides, Links…

  93. Q & A await