SlideShare a Scribd company logo
Async demystified
NDC Sydney 2019
Karel Zikmund – @ziki_cz
Agenda
• History of async patterns in .NET
• History and evolution of
• Task
• async-await
APM pattern:
Asynchronous Programming Model
.NET Framework 1.0/1.1 … 2002-2003
interface IAsyncResult
{
bool IsCompleted;
bool IsCompletedSynchronously;
object AsyncState;
WaitHandle AsyncWaitHandle;
}
Across BCL:
IAsyncResult BeginFoo(..., AsyncCallback callback, object state);
void EndFoo(IAsyncResult iar);
APM pattern
IAsyncResult BeginFoo(..., AsyncCallback callback, object state);
void EndFoo(IAsyncResult iar);
Synchronous call:
Foo();
Achieving the same:
EndFoo(BeginFoo(..., null, null));
Taking advantage of the asynchronous nature:
BeginFoo(..., iar => {
T val = EndFoo(iar);
// do stuff ...
});
APM – Example
Copy stream to stream:
int bytesRead;
while ((bytesRead = input.Read(buffer)) != 0) {
output.Write(buffer, 0 /* offset */, bytesRead);
}
APM – Nesting problem
BeginRead(..., iar => {
int bytesRead = EndRead(iar);
input.BeginWrite(..., iar2 => {
int bytesWritten2 = EndWrite(iar2);
BeginRead(..., iar3 => {
int bytesRead3 = EndRead(iar3);
BeginWrite(..., iar4 => {
// ... again and again
});
});
});
});
APM – IsCompletedSynchronously
IAsyncResult r = BeginRead(..., iar => {
if (!iar.IsCompletedSynchronously) {
// ... asynchronous path as shown earlier
}
});
if (r.IsCompletedSynchronously) {
// ... Synchronous path
}
• Even worse in loop
• Overall very complicated
• Queueing on ThreadPool much simpler
EAP:
Event-based Asynchronous Pattern
• .NET Framework 2.0
obj.Completed += (sender, eventArgs) => {
// ... my event handler
}
obj.SendPacket(); // returns void
• Did not solve multiple-calls problem, or loops
• Introduced ExecutionContext
Task
• .NET Framework 4.0
• MSR project – parallel computing
• Divide & conquer efficiently (e.g. QuickSort)
• Shaped Task – similar to today
• Task – represents general work (compute, I/O bound, etc.)
= promise / future / other terminology
• Task / Task<T> – operation (with optional result T) contains:
1. T … in the case of Task<T>
2. State related to synchronization
3. State related to callback
Task / TaskCompletionSource
• Task
• Here's a callback, invoke it when you're done, or right now if you've already
completed
• I want to block here, until your work is done
• Cannot be completed by user directly
• TaskCompletionSource … wrapper for Task
• Holds Task internally and operates on it via internal methods
• Methods:
• SetResult
• SetException
• SetCancelled
Task – Consumption
Task<T> t;
Either:
t.Wait(); // Blocks until Task is completed
Or:
t.ContinueWith(callback); // Will be executed after Task is completed
Even multiple times:
t.ContinueWith(callback2);
t.ContinueWith(callback3);
ContinueWith:
• Does not guarantee order of executions
• Always asynchronous (queued to ThreadPool/scheduler in general)
Task.Run
We complicated things 
Task<T> Task.Run(delegate d)
• Adds field to Task with ‘d’
• Queues work to ThreadPool
• Thread grabs it, executes it, marks task completed
Task.Run implementation
Task<T> Run(Func<T> f) {
var tcs = new TaskCompletionSource<T>();
ThreadPool.QueueUserWorkItem(() => {
try {
T result = f();
tcs.SetResult(result);
} catch (ex) {
tcs.SetException(ex);
}
});
return tcs.Task;
}
async-await
.NET Framework 4.5 / C# 5
Example of asynchronous code:
Task<int> GetDataAsync();
Task PutDataAsync(int i);
Code:
Task<int> t = GetDataAsync();
t.ContinueWith(a => {
var t2 = PutDataAsync(a.Result);
t2.ContinueWith(b => Console.WriteLine("done"));
});
async-await
Task<int> t = GetDataAsync();
t.ContinueWith(a => {
var t2 = PutDataAsync(a.Result);
t2.ContinueWith(b => Console.WriteLine("done"));
});
C# 5 with async-await helps us:
Task<int> t = GetDataAsync();
int aResult = await t;
Task t2 = PutDataAsync(aResult);
await t2;
Console.WriteLine("done");
Awaiter pattern
int aResult = await t;
Translated to:
var $awaiter1 = t.GetAwaiter();
if (! $awaiter1.IsCompleted) { // returns bool
// ... (complicated) ...
}
int aResult = $awaiter1.GetResult(); // returns void or T
// If exception, it will throw it
Awaiter pattern – details
void MoveNext() {
if (__state == 0) goto label0;
if (__state == 1) goto label1;
if (__state == 42) goto label42;
if (! $awaiter1.IsCompleted) {
__state = 42;
$awaiter1.OnCompleted(MoveNext);
return;
}
label42:
int aResult = $awaiter1.GetResult();
}
State Machine
string x = Console.ReadLine();
int aResult = await t;
Console.WriteLine("done" + x);
State machine:
struct MethodFooStateMachine {
void MoveNext() { ... }
local1; // would be ‘x’ in example above
local2;
params;
_$awaiter1;
__state;
}
State Machine – Example
public async Task Foo(int timeout) {
await Task.Delay(timeout);
}
Compiler generates:
public Task Foo(int timeout) {
FooStateMachine sm = default;
sm._timeout = timeout;
sm._state = 0;
sm.MoveNext();
return ???;
}
struct FooStateMachine {
int _timeout; // param
// locals would be here too
void MoveNext() { ... }
int __state;
TaskAwaiter _$awaiter1;
}
State Machine – Example
public Task Foo(int timeout) {
FooStateMachine sm = default;
sm._tcs = new TaskCompletionSource();
sm._timeout = timeout;
sm._state = 0;
sm.MoveNext();
return sm._tcs.Task;
}
Builder pattern (can return struct):
AsyncValueTaskMethodBuilder.Create();
_tcs.Task -> _builder.Task;
struct FooStateMachine {
int _timeout; // param
// locals would be here too
void MoveNext() {
// ...
_tcs.SetResult(...);
}
int _state;
TaskAwaiter _$awaiter1;
TaskCompletionSource _tcs;
}
State Machine – Perf improvements
What about Task allocation?
• Builder can reuse known tasks
• Task.CompletedTask (without value)
• boolean – True/False
• int … <-1,8>
• LastCompleted (e.g. on MemoryStream)
• Does not work on SslStream (alternates headers and body)
• Size: 64B (no value) / 72B (with value)
• Azure workloads OK (GC will collect)
• Hot-path: up to 5%-10% via more GCs
ValueTask
• .NET Core 2.0
• Also as nuget package down-level
struct ValueTask<T> {
T;
Task<T>;
}
• Only one of them: T+null or default+Task<T>
• NET Core 2.1
ValueTask<int> Stream.ReadAsync(Memory<byte>, ...)
ValueTask – Can we improve more?
• What about the 1% asynchronous case?
• .NET Core 2.1
struct ValueTask<T> {
T;
Task<T>;
IValueTaskSource<T>;
}
struct ValueTask {
Task;
IValueTaskSource;
}
Summary
• APM pattern = Asynchronous Programming Model
• .NET Framework 1.0/1.1 (2002-2003)
• IAsyncResult, BeginFoo/EndFoo
• Limited nesting / loops
• EAP = Event-based Asynchronous Pattern (.NET Framework 2.0)
• Events – similar problems as APM
• Task (.NET Framework 4.0)
• Wait / ContinueWith
• TaskCompletionSource (for control/writing)
• async-await (.NET Framework 4.5 / C# 5)
• Awaiter pattern, state machine
• ValueTask (.NET Core 2.0)
• Don’t use unless you are on hot-path
• Hyper-optimizations possible, stay away, it is dangerous! 
@ziki_cz

More Related Content

PPTX
.NET Core Summer event 2019 in Brno, CZ - Async demystified -- Karel Zikmund
PPTX
History of asynchronous in .NET
PDF
Job Queue in Golang
PDF
JVMLS 2016. Coroutines in Kotlin
PPTX
CSharp for Unity Day2
PDF
Kotlin coroutine - the next step for RxJava developer?
PDF
Deep Dive async/await in Unity with UniTask(EN)
PPTX
Async programming and python
.NET Core Summer event 2019 in Brno, CZ - Async demystified -- Karel Zikmund
History of asynchronous in .NET
Job Queue in Golang
JVMLS 2016. Coroutines in Kotlin
CSharp for Unity Day2
Kotlin coroutine - the next step for RxJava developer?
Deep Dive async/await in Unity with UniTask(EN)
Async programming and python

Similar to NDC Sydney 2019 - Async Demystified -- Karel Zikmund (20)

PPTX
C#을 이용한 task 병렬화와 비동기 패턴
PPTX
Grand Central Dispatch in Objective-C
PDF
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
PPTX
C++ Functions
PDF
Appsec obfuscator reloaded
PDF
Advanced patterns in asynchronous programming
PPTX
Async and parallel patterns and application design - TechDays2013 NL
PDF
Asynchronní programování
PPTX
Binary Studio Academy: Concurrency in C# 5.0
PDF
Think Async: Asynchronous Patterns in NodeJS
PDF
Using zone.js
PDF
Kotlin - Coroutine
PDF
Coroutines in Kotlin. In-depth review
PDF
Coroutines in Kotlin. UA Mobile 2017.
PDF
.NET Multithreading and File I/O
PPTX
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
DOCX
In Class AssignmetzCST280W13a-1.pdfCST 280 In-Class Pract.docx
PDF
Asynchronous programming done right - Node.js
PDF
PDF
Programming For Big Data [ Submission DvcScheduleV2.cpp and StaticA.pdf
C#을 이용한 task 병렬화와 비동기 패턴
Grand Central Dispatch in Objective-C
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
C++ Functions
Appsec obfuscator reloaded
Advanced patterns in asynchronous programming
Async and parallel patterns and application design - TechDays2013 NL
Asynchronní programování
Binary Studio Academy: Concurrency in C# 5.0
Think Async: Asynchronous Patterns in NodeJS
Using zone.js
Kotlin - Coroutine
Coroutines in Kotlin. In-depth review
Coroutines in Kotlin. UA Mobile 2017.
.NET Multithreading and File I/O
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
In Class AssignmetzCST280W13a-1.pdfCST 280 In-Class Pract.docx
Asynchronous programming done right - Node.js
Programming For Big Data [ Submission DvcScheduleV2.cpp and StaticA.pdf
Ad

More from Karel Zikmund (20)

PPTX
.NET Conf 2022 - Networking in .NET 7
PPTX
NDC London 2020 - Challenges of Managing CoreFx Repo -- Karel Zikmund
PPTX
WUG Days 2022 Brno - Networking in .NET 7.0 and YARP -- Karel Zikmund
PDF
.NET Core Summer event 2019 in Vienna, AT - .NET 5 - Future of .NET on Mobile...
PPTX
.NET Core Summer event 2019 in Linz, AT - War stories from .NET team -- Karel...
PPTX
.NET Core Summer event 2019 in Brno, CZ - .NET Core Networking stack and perf...
PPTX
.NET Core Summer event 2019 in Brno, CZ - War stories from .NET team -- Karel...
PPTX
.NET Core Summer event 2019 in Prague, CZ - War stories from .NET team -- Kar...
PPTX
.NET Core Summer event 2019 in Vienna, AT - War stories from .NET team -- Kar...
PPTX
.NET Core Summer event 2019 in NL - War stories from .NET team -- Karel Zikmund
PPTX
NDC Oslo 2019 - War stories from .NET team -- Karel Zikmund
PPTX
DotNext 2017 in Moscow - Challenges of Managing CoreFX repo -- Karel Zikmund
PPTX
DotNext 2017 in Moscow - .NET Core Networking stack and Performance -- Karel ...
PPTX
.NET MeetUp Brno 2017 - Microsoft Engineering teams in Europe -- Karel Zikmund
PPTX
.NET MeetUp Brno 2017 - Xamarin .NET internals -- Marek Safar
PPTX
.NET MeetUp Brno - Challenges of Managing CoreFX repo -- Karel Zikmund
PPTX
.NET Fringe 2017 - Challenges of Managing CoreFX repo -- Karel Zikmund
PPTX
.NET MeetUp Prague 2017 - Challenges of Managing CoreFX repo -- Karel Zikmund
PPTX
.NET MeetUp Prague 2017 - .NET Standard -- Karel Zikmund
PPTX
.NET MeetUp Amsterdam 2017 - Challenges of Managing CoreFX repo -- Karel Zikmund
.NET Conf 2022 - Networking in .NET 7
NDC London 2020 - Challenges of Managing CoreFx Repo -- Karel Zikmund
WUG Days 2022 Brno - Networking in .NET 7.0 and YARP -- Karel Zikmund
.NET Core Summer event 2019 in Vienna, AT - .NET 5 - Future of .NET on Mobile...
.NET Core Summer event 2019 in Linz, AT - War stories from .NET team -- Karel...
.NET Core Summer event 2019 in Brno, CZ - .NET Core Networking stack and perf...
.NET Core Summer event 2019 in Brno, CZ - War stories from .NET team -- Karel...
.NET Core Summer event 2019 in Prague, CZ - War stories from .NET team -- Kar...
.NET Core Summer event 2019 in Vienna, AT - War stories from .NET team -- Kar...
.NET Core Summer event 2019 in NL - War stories from .NET team -- Karel Zikmund
NDC Oslo 2019 - War stories from .NET team -- Karel Zikmund
DotNext 2017 in Moscow - Challenges of Managing CoreFX repo -- Karel Zikmund
DotNext 2017 in Moscow - .NET Core Networking stack and Performance -- Karel ...
.NET MeetUp Brno 2017 - Microsoft Engineering teams in Europe -- Karel Zikmund
.NET MeetUp Brno 2017 - Xamarin .NET internals -- Marek Safar
.NET MeetUp Brno - Challenges of Managing CoreFX repo -- Karel Zikmund
.NET Fringe 2017 - Challenges of Managing CoreFX repo -- Karel Zikmund
.NET MeetUp Prague 2017 - Challenges of Managing CoreFX repo -- Karel Zikmund
.NET MeetUp Prague 2017 - .NET Standard -- Karel Zikmund
.NET MeetUp Amsterdam 2017 - Challenges of Managing CoreFX repo -- Karel Zikmund
Ad

Recently uploaded (20)

PDF
A REACT POMODORO TIMER WEB APPLICATION.pdf
PDF
How to Confidently Manage Project Budgets
PPTX
AIRLINE PRICE API | FLIGHT API COST |
PPTX
Safe Confined Space Entry Monitoring_ Singapore Experts.pptx
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
Micromaid: A simple Mermaid-like chart generator for Pharo
PPTX
How a Careem Clone App Allows You to Compete with Large Mobility Brands
PDF
Best Smart Port Software of 2025 Why Envision Leads the Market.pdf
PPTX
Materi_Pemrograman_Komputer-Looping.pptx
PDF
AI in Product Development-omnex systems
PPTX
Hire Expert WordPress Developers from Brainwings Infotech
PPTX
Mastering-Cybersecurity-The-Crucial-Role-of-Antivirus-Support-Services.pptx
PDF
Microsoft Teams Essentials; The pricing and the versions_PDF.pdf
PDF
IEEE-CS Tech Predictions, SWEBOK and Quantum Software: Towards Q-SWEBOK
PPTX
Odoo Consulting Services by CandidRoot Solutions
PDF
Forouzan Book Information Security Chaper - 1
PDF
Convert Thunderbird to Outlook into bulk
PDF
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
ShowUs: Pharo Stream Deck (ESUG 2025, Gdansk)
A REACT POMODORO TIMER WEB APPLICATION.pdf
How to Confidently Manage Project Budgets
AIRLINE PRICE API | FLIGHT API COST |
Safe Confined Space Entry Monitoring_ Singapore Experts.pptx
How to Migrate SBCGlobal Email to Yahoo Easily
Micromaid: A simple Mermaid-like chart generator for Pharo
How a Careem Clone App Allows You to Compete with Large Mobility Brands
Best Smart Port Software of 2025 Why Envision Leads the Market.pdf
Materi_Pemrograman_Komputer-Looping.pptx
AI in Product Development-omnex systems
Hire Expert WordPress Developers from Brainwings Infotech
Mastering-Cybersecurity-The-Crucial-Role-of-Antivirus-Support-Services.pptx
Microsoft Teams Essentials; The pricing and the versions_PDF.pdf
IEEE-CS Tech Predictions, SWEBOK and Quantum Software: Towards Q-SWEBOK
Odoo Consulting Services by CandidRoot Solutions
Forouzan Book Information Security Chaper - 1
Convert Thunderbird to Outlook into bulk
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
How Creative Agencies Leverage Project Management Software.pdf
ShowUs: Pharo Stream Deck (ESUG 2025, Gdansk)

NDC Sydney 2019 - Async Demystified -- Karel Zikmund

  • 1. Async demystified NDC Sydney 2019 Karel Zikmund – @ziki_cz
  • 2. Agenda • History of async patterns in .NET • History and evolution of • Task • async-await
  • 3. APM pattern: Asynchronous Programming Model .NET Framework 1.0/1.1 … 2002-2003 interface IAsyncResult { bool IsCompleted; bool IsCompletedSynchronously; object AsyncState; WaitHandle AsyncWaitHandle; } Across BCL: IAsyncResult BeginFoo(..., AsyncCallback callback, object state); void EndFoo(IAsyncResult iar);
  • 4. APM pattern IAsyncResult BeginFoo(..., AsyncCallback callback, object state); void EndFoo(IAsyncResult iar); Synchronous call: Foo(); Achieving the same: EndFoo(BeginFoo(..., null, null)); Taking advantage of the asynchronous nature: BeginFoo(..., iar => { T val = EndFoo(iar); // do stuff ... });
  • 5. APM – Example Copy stream to stream: int bytesRead; while ((bytesRead = input.Read(buffer)) != 0) { output.Write(buffer, 0 /* offset */, bytesRead); }
  • 6. APM – Nesting problem BeginRead(..., iar => { int bytesRead = EndRead(iar); input.BeginWrite(..., iar2 => { int bytesWritten2 = EndWrite(iar2); BeginRead(..., iar3 => { int bytesRead3 = EndRead(iar3); BeginWrite(..., iar4 => { // ... again and again }); }); }); });
  • 7. APM – IsCompletedSynchronously IAsyncResult r = BeginRead(..., iar => { if (!iar.IsCompletedSynchronously) { // ... asynchronous path as shown earlier } }); if (r.IsCompletedSynchronously) { // ... Synchronous path } • Even worse in loop • Overall very complicated • Queueing on ThreadPool much simpler
  • 8. EAP: Event-based Asynchronous Pattern • .NET Framework 2.0 obj.Completed += (sender, eventArgs) => { // ... my event handler } obj.SendPacket(); // returns void • Did not solve multiple-calls problem, or loops • Introduced ExecutionContext
  • 9. Task • .NET Framework 4.0 • MSR project – parallel computing • Divide & conquer efficiently (e.g. QuickSort) • Shaped Task – similar to today • Task – represents general work (compute, I/O bound, etc.) = promise / future / other terminology • Task / Task<T> – operation (with optional result T) contains: 1. T … in the case of Task<T> 2. State related to synchronization 3. State related to callback
  • 10. Task / TaskCompletionSource • Task • Here's a callback, invoke it when you're done, or right now if you've already completed • I want to block here, until your work is done • Cannot be completed by user directly • TaskCompletionSource … wrapper for Task • Holds Task internally and operates on it via internal methods • Methods: • SetResult • SetException • SetCancelled
  • 11. Task – Consumption Task<T> t; Either: t.Wait(); // Blocks until Task is completed Or: t.ContinueWith(callback); // Will be executed after Task is completed Even multiple times: t.ContinueWith(callback2); t.ContinueWith(callback3); ContinueWith: • Does not guarantee order of executions • Always asynchronous (queued to ThreadPool/scheduler in general)
  • 12. Task.Run We complicated things  Task<T> Task.Run(delegate d) • Adds field to Task with ‘d’ • Queues work to ThreadPool • Thread grabs it, executes it, marks task completed
  • 13. Task.Run implementation Task<T> Run(Func<T> f) { var tcs = new TaskCompletionSource<T>(); ThreadPool.QueueUserWorkItem(() => { try { T result = f(); tcs.SetResult(result); } catch (ex) { tcs.SetException(ex); } }); return tcs.Task; }
  • 14. async-await .NET Framework 4.5 / C# 5 Example of asynchronous code: Task<int> GetDataAsync(); Task PutDataAsync(int i); Code: Task<int> t = GetDataAsync(); t.ContinueWith(a => { var t2 = PutDataAsync(a.Result); t2.ContinueWith(b => Console.WriteLine("done")); });
  • 15. async-await Task<int> t = GetDataAsync(); t.ContinueWith(a => { var t2 = PutDataAsync(a.Result); t2.ContinueWith(b => Console.WriteLine("done")); }); C# 5 with async-await helps us: Task<int> t = GetDataAsync(); int aResult = await t; Task t2 = PutDataAsync(aResult); await t2; Console.WriteLine("done");
  • 16. Awaiter pattern int aResult = await t; Translated to: var $awaiter1 = t.GetAwaiter(); if (! $awaiter1.IsCompleted) { // returns bool // ... (complicated) ... } int aResult = $awaiter1.GetResult(); // returns void or T // If exception, it will throw it
  • 17. Awaiter pattern – details void MoveNext() { if (__state == 0) goto label0; if (__state == 1) goto label1; if (__state == 42) goto label42; if (! $awaiter1.IsCompleted) { __state = 42; $awaiter1.OnCompleted(MoveNext); return; } label42: int aResult = $awaiter1.GetResult(); }
  • 18. State Machine string x = Console.ReadLine(); int aResult = await t; Console.WriteLine("done" + x); State machine: struct MethodFooStateMachine { void MoveNext() { ... } local1; // would be ‘x’ in example above local2; params; _$awaiter1; __state; }
  • 19. State Machine – Example public async Task Foo(int timeout) { await Task.Delay(timeout); } Compiler generates: public Task Foo(int timeout) { FooStateMachine sm = default; sm._timeout = timeout; sm._state = 0; sm.MoveNext(); return ???; } struct FooStateMachine { int _timeout; // param // locals would be here too void MoveNext() { ... } int __state; TaskAwaiter _$awaiter1; }
  • 20. State Machine – Example public Task Foo(int timeout) { FooStateMachine sm = default; sm._tcs = new TaskCompletionSource(); sm._timeout = timeout; sm._state = 0; sm.MoveNext(); return sm._tcs.Task; } Builder pattern (can return struct): AsyncValueTaskMethodBuilder.Create(); _tcs.Task -> _builder.Task; struct FooStateMachine { int _timeout; // param // locals would be here too void MoveNext() { // ... _tcs.SetResult(...); } int _state; TaskAwaiter _$awaiter1; TaskCompletionSource _tcs; }
  • 21. State Machine – Perf improvements What about Task allocation? • Builder can reuse known tasks • Task.CompletedTask (without value) • boolean – True/False • int … <-1,8> • LastCompleted (e.g. on MemoryStream) • Does not work on SslStream (alternates headers and body) • Size: 64B (no value) / 72B (with value) • Azure workloads OK (GC will collect) • Hot-path: up to 5%-10% via more GCs
  • 22. ValueTask • .NET Core 2.0 • Also as nuget package down-level struct ValueTask<T> { T; Task<T>; } • Only one of them: T+null or default+Task<T> • NET Core 2.1 ValueTask<int> Stream.ReadAsync(Memory<byte>, ...)
  • 23. ValueTask – Can we improve more? • What about the 1% asynchronous case? • .NET Core 2.1 struct ValueTask<T> { T; Task<T>; IValueTaskSource<T>; } struct ValueTask { Task; IValueTaskSource; }
  • 24. Summary • APM pattern = Asynchronous Programming Model • .NET Framework 1.0/1.1 (2002-2003) • IAsyncResult, BeginFoo/EndFoo • Limited nesting / loops • EAP = Event-based Asynchronous Pattern (.NET Framework 2.0) • Events – similar problems as APM • Task (.NET Framework 4.0) • Wait / ContinueWith • TaskCompletionSource (for control/writing) • async-await (.NET Framework 4.5 / C# 5) • Awaiter pattern, state machine • ValueTask (.NET Core 2.0) • Don’t use unless you are on hot-path • Hyper-optimizations possible, stay away, it is dangerous!  @ziki_cz

Editor's Notes

  • #3: Internal talk Warning: Some people find the 1st part as a bit boring recap, some like it
  • #4: IAsyncResult AsyncWaitHandle – ManualResetEvent or AutoResetEvent Across BCL Usage of the APIs either: Wait for callback to be called, or Call EndFoo which will block until completed
  • #5: END: Single operation works fine, but in reality you do more – e.g. in a loop
  • #6: E.g. Network to disk
  • #7: END: Manually it does not work – somehow turn it into loop It’s possible but extremely long and tricky Further complications with IsCompletedSynchronously (as perf optimization)… next slide
  • #8: For perf reasons – handle synchronous completions right away At the end: Imagine you did the loop with this as before … on MemoryStream, the data is already available -> instead of ThreadPool, call delegate immediately -> Leads to recursive calls -> ~10K StackOverflow Bottom part: Even BCL has lots of wrappers (e.g. in Networking: LazyAsyncResult) with lots of specializations Very complicated
  • #9: Straightforward idea – Completed event Kick off operation, then Completed handler is invoked (generally on ThreadPool) END: ExecutionContext – basically a ThreadLocal, which survived until much later and until today in some form BCL: Used in 5-10 classes in BCL … like SmtpMail, TcpClient, BackgroundWorker Downsides: We shipped it in .NET Framework 2.0 and quickly realized that it is interesting experiment, but not exactly addressing real needs
  • #10: #2 -- “MSR Project” 90% right, 10% keeps Toub awake at night even after 10 years and would love to change it #3 – “Task” NOT tied to ThreadPool – not tied to executing delegate Shove result into it Can be completed Can wake up someone waiting on it
  • #11: #1 – Task Task – something to consume - hook up to #1.3 – Cannot be completed - not to change directly (no control) #2: TaskCompletionSource – can alter state of Task … has control over Task Example: Lazy initialization (something over network) … you are in charge who can change “work completed” You don’t want others to decide when it is done
  • #12: Wait - creates ManualResetEvent which will be signaled when one of the SetResult/SetException/SetCancelled is called END: Option: TaskExecutionOption to do it synchronously APM (IAsyncResult) … no shared implementation, everyone had to have their own implementation Task model - you don't pass delegate at creation, but you can walk up on any of them and say "call me when you're done" Enabled abstractions – like async-await await hooks up the callback … we will look into it later
  • #13: Convenient method – but mixes up things ties it to ThreadPool (breaks abstraction) adds delegate to Task END: Sets completed = execute callback, waking up things waiting on it, etc.
  • #14: #1 – TaskCompletionSource creates Task #3 – return - returns Task to be awaited on, etc. Now we implemented Task.Run without storing any delegate on Task
  • #15: #2 - GetData/PutData … maybe across the network
  • #16: END: Compiler translates it to the code above (hand-waving involved) Note: Compiler does not treat Task specially, but it just looks for pattern (awaiter pattern)
  • #17: #3 – GetResult Bold methods are pattern matching #4 – complicated comment Simplified version, things are in fact more complicated “! IsCompleted” part is complicated – I have to hook up code that comes back here when task completes
  • #18: #0 – Let’s look deeper into the “! IsCompleted” #1 – All of it is part of MoveNext method -- it is a state machine, every await in method is state in state machine (hand waving a bit) END: OnCompleted has slightly more complicated signature
  • #19: #1 – code How does ‘x’ survive? (it is just on stack) – need to capture it Same in lambda – C# compiler lifts local to keep it on heap allocated object (floats through closures) Same in state machine END: Compiler optimizes – stores here things only crossing await boundary Note that in debug -- struct is class -- for debuggability, but for perf struct Why struct? These async methods often complete synchronously – example: BufferedStream … large buffer behind with inner stream If I ask for 1B, but it reads 10K in bulk, then lots of calls finish synchronously If it was class, then we would allocate per call
  • #20: END: We will talk about return ??? (a Task) in more details on next slide Let’s expand the generated code a bit – Foo and FooStateMachine
  • #21: #1 – _tcs _tcs (producers) is logically on state machine (a bit more complicated) … reminder: it has Task inside #2 – new TaskCompletionSource Allocated per call / operation Problem #1: 2 allocations – TaskCompletionSource and Task (inside) For the synchronous case we want 0 allocations ideally (BufferedStream example) Problem #2: Even the Task/TaskCompletionSource is problematic, because it is anything Task-like (compiler does not want to hardcore Task) … builder pattern instead of constructor new TSC() Each Task-like type (except Task) has attribute defining builder - builder pattern ValueTask (details later) has one -> AsyncValueTaskMethodBuilder instead of new TaskCompletionSource() -> AsyncValueTaskMethodBuilder.Create(); We have internally in System.Runtime.CompilerServices structs: AsyncTaskMethodBuilder, AsyncTaskMethodBuilder<T>, AsyncVoidMethodBuilder #5 -- Task instead of _tcs.Task -> _builder.Task We eliminated TaskCompletionSource allocation – builder can return struct (ValueTask) END: What about the Task?
  • #22: CompletedTask – void return value – just info it finished vs. not END: How can we improve the perf even more?
  • #23: #2 “Only one of them” Methods are 1-liners (if Task<T> == null, do something, else something else) Nicely handles synchronously completing case Note: Non-generic ValueTask does not make sense - only Task inside … we have CompletedTask Problem: Overloading on return type #3 – .NET Core 2.1 Luckily we introduced Memory<T> at the same time, as we cannot overload on return type That's why sometimes in PRs we wrap byte[] in Memory first … to use the ValueTask Design-guidelines: Start with Task … use ValueTask only in hot-path scenarios
  • #24: IValueTaskSource – complicated interface almost the awaiter pattern: Are you completed? Hook up a call back Get a result All implementations on ValueTask are now ternary Value: You can implement it however you want, incl. reset (reuse) Complicated to do, so not everywhere Socket.SendAsync/ReceiveAsync Object per send/receive … but one at a time (typical) 0 allocation for loop around Send/Receive on Socket Same: NetworkStream, Pipelines, Channels