Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and...
Transcript of Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and...
![Page 2: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/2.jpg)
Throughout the entire process, a key takeaway is that no thread is dedicated to running the task.H T T P S : / / D O C S . M I C R O S O F T.C O M / E N - U S / D OT N E T/ S TA N D A R D /A S Y N C - I N - D E P T H
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 2
![Page 3: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/3.jpg)
About me
.NET developer for 5 years.
Scala Developer at Amazon.
Blogger, public speaker.
Author of .NET Internals Cookbook.
http://blog.adamfurmanek.pl
furmanekadam
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 3
![Page 4: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/4.jpg)
Don’t forget to evaluatethis session in the DevSum app!
#DevSum19
![Page 5: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/5.jpg)
AgendaPrimitives under the hood.
Task details.
SynchronizationContext internals.
State machine.
Waiting for async void and handling exceptions.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 5
![Page 6: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/6.jpg)
Primitives under the hood
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 6
![Page 7: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/7.jpg)
Native threadTwo types: foreground and background (don’t stop application from terminating).
Consists of Thread Kernel Object, two stacks (user mode and kernel mode) and Thread Environment Block (TEB).
User mode stack by default has 1 MB, kernel mode has 12/24 KB.
Has impersonation info, security context, Thread Local Storage (TLS).
Windows schedules threads, not processes!
How many threads does the notepad.exe have?
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 7
![Page 8: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/8.jpg)
How many threads does a notepad have?
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 8
![Page 9: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/9.jpg)
Managed threadHas ID independent of native thread ID.
Can have name.
Can be suspended but this should not be done! Can be aborted by Thread.Abort but this doesn’t guarantee anything.
Precommits stack when created.
Unhandled exception kills the application in most cases.
In .NET 1 it was different:◦ Exception in other thread was printed to the console and
the thread was terminated.◦ Exception on the finalizer was printed to the console and
finalizer was still working.◦ Exception on the main thread resulted in application
termination.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 9
![Page 10: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/10.jpg)
ThreadPoolDifferent from Win32 thread pool. Used by tasks, asynchronous timers, wait handles and ThreadPool.QueueUserWorkItem.
Static class – you cannot just create your own thread pool.
Threads work in background, do not clear the TLS, have default stack size and default priority.
One pool per process, its size depends on the size of the virtual address space. Threads are created and destroyed as needed using hill climbing algorithm.
Two types of threads: for ordinary callbacks and for I/O operations.
Thrown exception is held until awaiting and then propagated if possible (thrown out of band for async void). In .NET 1 it was different - exception on a thread pool was printed to the console and the thread was returned to the pool.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 10
http://aviadezra.blogspot.com/2009/06/net-clr-thread-pool-work.html
![Page 11: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/11.jpg)
ThreadPool implementationpublic static class SimpleThreadPool{
private static BlockingCollection<Action> _work = new BlockingCollection<Action>();static SimpleThreadPool() {
for (int i = 0; i < Environment.ProcessorCount; i++) {new Thread(() => {foreach (var action in _work.GetConsumingEnumerable()) {
action();}
}) { IsBackground = true }.Start();}
}public static void QueueWorkItem(Action workItem) { _work.Add(workItem); }
}
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 11
![Page 12: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/12.jpg)
Asynchronous Programming Model (APM)BeginOperation returns an object implementing IAsyncResult.
◦ Triggers the asynchronous calculations on different thread.
◦ Can also accept a callback to be called when the operation is finished.
IAsyncResult:◦ Has some AsyncState.◦ Contains WaitHandle which we can use to block the
application.◦ Has flag indicating whether the operation is
completed.
EndOperation accepts IAsyncResult as a parameter and returns the same as synchronous counterpart.
◦ Throws all exceptions if needed.◦ If the operation hasn’t finished, blocks the thread.
var fs = new FileStream(@"C:\file.txt");
byte[] data = new byte[100];
fs.BeginRead(data, 0, data.Length, (IAsyncResult ar) =>
{
int bytesRead = fs.EndRead(ar);
fs.Close();
}, null
);
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 12
![Page 13: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/13.jpg)
Event-based Asynchronous Pattern (EAP)MethodNameAsync.
◦ Triggers the operation on a separate thread.
MethodNameCompleted.
◦ Event fired when the operation finishes.
◦ Passes parameter AsyncCompletedEventArgs.
AsyncCompletedEventArgs:
◦ Contains flag if the job was cancelled.
◦ Contains all the errors.
◦ Has some UserState.
Can be canceled.
Can be used easily with BackgroundWorker.
backgroundWorker.DoWork += backgroundWorker_DoWork;
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// ...
}
private voidbackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// ...
}
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 13
![Page 14: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/14.jpg)
Task-based Asynchronous Pattern (TAP)Task.Run accepting delegate triggers the job:
◦ Equivalent toTask.Factory.StartNew(job, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
◦ Unwraps the result if needed (so we get Task<int> instead of Task<Task<int>>).
Task can be created manually via constructor and schedulled using Start method.
Can be joined by using ContinueWith.
Exceptions are caught and propagated on continuation.
Can be used with TaskCompletionSource.
Can be cancelled with CancellationToken.
Can report progress using IProgress<T>.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 14
![Page 15: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/15.jpg)
Parallel Language Integrated Queries (PLINQ)Created when AsParallel called on IEnumerable. Can be reverted by AsSequential.
Operations defined in ParallelEnumerable class.
Can be ordered by calling AsOrdered.
Task merging can be configured by specifying ParallelMergeOptions.
Maximum number of concurrent tasks can be controlled using WithDegreeOfParallelism.
Parallelism is not mandatory! Can be forced with ParallelExcecutionMode.
Each AsParallel call reshuffles the tasks.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 15
![Page 16: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/16.jpg)
async and awaitawait can be executed on anything awaitable – not necessarily a Task!
◦ Task.Yield returns YieldAwaitable
Duck typing - awaitable type must be able to return GetAwaiter() with the following:◦ Implements INotifyCompletion interface
◦ bool IsCompleted { get; }
◦ void OnCompleted(Action continuation);
◦ TResult GetResult(); // or void
async means nothing — it only instructs the compiler to create a state machine.
We can make any type awaitable using extension methods!
Very similar to foreach.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 16
![Page 17: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/17.jpg)
Awaiting on integer
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 17
![Page 18: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/18.jpg)
Task details
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 18
![Page 19: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/19.jpg)
Two types of tasksDELEGATE TASK – CPU-BOUND
Has some code to run.
Mostly in TPL world.
Created by TaskFactory or by constructor.
Used in PLINQ.
Can be scheduled and executed.
PROMISE TASK – I/O-BOUND
Signals completion of something.
Mostly in async world.
Task.FromResult◦ Creates completed Task with result.
Task.Delay◦ Equivalent of Thread.Sleep.
Task.Yield◦ Returns YieldAwaitable.
◦ Schedules continuation immediately
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 19
![Page 20: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/20.jpg)
Task state
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 20
https://blog.stephencleary.com/2014/06/a-tour-of-task-part-3-status.html
![Page 21: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/21.jpg)
Task creationCONSTRUCTOR
Do not use!Creates only delegate Task.
Created Task is not scheduled so will not start running unless asked to.
Created Task can be started by calling Startmethod (and optionally providing a scheduler).
FACTORY
Task.Run()
Task.Factory.StartNew()
PLINQ
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 21
![Page 22: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/22.jpg)
Task.ScheduleAndStart
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 22
![Page 23: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/23.jpg)
TaskSchedulerSchedules tasks on the threads – it makes sure that the work of a task is eventually executed.
For TPL and PLINQ is based on the thread pool.
Supports work-stealing, thread injection/retirement and fairness.
Two types of queues:◦ Global – for top level tasks
◦ Local – for nested/child tasks, accessed in LIFO order
Long running tasks are handled separately, do not go via global/local queue.
We can implement our own schedulers.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 23
![Page 24: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/24.jpg)
TaskScheduler implementation
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 24
public class MyTaskScheduler : TaskScheduler{
private readonly MyContext context;public BlockingCollection<Task> tasks = new BlockingCollection<Task>();
protected override IEnumerable<Task> GetScheduledTasks(){
return tasks;}
protected override void QueueTask(Task task){
tasks.Add(task);}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued){
return TryExecuteTask(task);}
}
![Page 25: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/25.jpg)
Task.ContinueWithCreates a continuation that executes asynchronously when the target task complets
◦ Can specify CancellationToken
◦ Can specify TaskScheduler
◦ Can specify TaskContinuationOptions
Options:◦ OnlyOnCompletion, OnlyOnCanceled, OnlyOnFaulted, NotOnCanceled, NotOnFaulted,
NotOnCompletion – to choose when it is supposed to run
◦ AttachedToParent – to create hierarchy of tasks
◦ ExecuteSynchronously, RunContinuationAsynchronously – to choose the thread running it
◦ HideScheduler – to run using the default scheduler instead of the current one
◦ LongRunning – more or less to run on dedicated thread
◦ Prefer fairness – to run in order
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 25
![Page 26: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/26.jpg)
Task.ContinueWith
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 26
![Page 27: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/27.jpg)
Task.Complete
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 27
![Page 28: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/28.jpg)
Task.TrySetResult
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 28
![Page 29: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/29.jpg)
Task.FinishContinuations
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 29
![Page 30: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/30.jpg)
StandardTaskContinuation.Run
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 30
![Page 31: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/31.jpg)
Disposing a TaskTask may allocated WaitHandle which implements IDisposable.
Disposing a Task in .NET 4 was making it unusable — we couldn’t even schedule continuation.
In .NET 4.5 this was changed, Task is still usable, only WaitHandle is not.
WaitHandle was created when Task.WaitAny or Task.WaitAll was called, this is no longer true.
Starting in .NET 4.5 WaitHandle is allocated only when it is explicitly accessed.
Summary:◦ .NET 4 — don’t dispose unless you have to. Do so only if you are sure that the Task will never be used
again.
◦ .NET 4.5 — it shouldn’t make a difference so probably don’t bother.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 31
![Page 32: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/32.jpg)
Task.Status Task.IdState IsCompleted IsCanceled IsFaulted
RanToCompletion True False False
Canceled True True False
Faulted True False True
Other False False False
Generated on demand.
Can be reused — you can generate collision!
Independent from TaskScheduler.Id.
0 is not a valid identifier.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 32
![Page 33: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/33.jpg)
ValueTaskTask is a class so it is allocated on the heap and needs to be collected by GC.
To avoid explicit allocation we can use ValueTask which is a struct and is allocated on the stack.
The trick is in the second constructor parameter — the token.
public ValueTask(IValueTaskSource<T> source, short token);
See https://github.com/kkokosa/PooledValueTaskSource
Conceptually it was used in Midori — .NET-based operating system implemented by Microsoft Research.
„It still kills me that I can’t go back in time and make .NET’s task a struct” — Joe Duffy in http://joeduffyblog.com/2015/11/19/asynchronous-everything/
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 33
![Page 34: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/34.jpg)
SynchronizationContextinternals
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 34
![Page 35: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/35.jpg)
ISynchronizeInvoke – life before SynchronizationContextProvides a way to synchronously or asynchronously execute a delegate:
◦ InvokeRequired – checks if invoking is requred, effectively if we are running on the same thread
◦ Invoke – synchronous invocation
◦ BeginInvoke, EndInvoke – asynchronous invocation
It ties communication and threads.
If we don’t need specific thread – as in ASP.NET – we should not use ISynchronizeInvoke.
This is how SynchronizationContext emerged.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 35
![Page 36: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/36.jpg)
ExecutionContext and otherBag holding logical context of the execution.
Contains SynchronizationContext, LogicalCallContext, SecurityContext, HostExecutionContext, CallContext etc.
Does not need to rely on Thread Local Storage (TLS) and is passed correctly through asynchronous points — will follow to the other thread.
Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation.
Starting in .NET 4.6 there is an AsyncLocal<T> class working as TLS variables for tasks.
Methods with Unsafe* do not propagate the context — for instance ThreadPool.UnsafeQueueUserWorkItem.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 36
![Page 37: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/37.jpg)
AsyncLocal<T>
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 37
![Page 38: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/38.jpg)
SynchronizationContextThe SynchronizationContext class is a base class that provides a free-threaded context with no synchronization:
◦ OperationStarted and OperationCompleted – handles notifications
◦ Send – synchronous message
◦ Post – asynchronous message
◦ Current – gets synchronization context for the thread
The purpose of the synchronization model implemented by this class is to allow the internal asynchronous/synchronous operations of the common language runtime to behave properly with different synchronization models
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 38
![Page 39: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/39.jpg)
SynchronizationContextWhen awaiting the awaitable type the current context is captured. Later, the rest of the method is posted on the context. Effectively, synchronization context is a global variable.
If SynchronizationContext.Current is not null then this context is captured:◦ For UI thread it is UI context – WindowsFormsSynchronizationContext, DispatcherSynchronizationContext,
WinRTSynchronizationContext, WinRTCoreDispatcherBasedSynchronizationContext◦ Implemented via event loop, for instance.
◦ For ASP.NET request it is ASP.NET context — AspNetSynchronizationContext◦ This can be different thread than original one, but still the request context is the same.
Otherwise it is current TaskScheduler:◦ TaskScheduler.Default is the thread pool context.
◦ ASP.NET Core doesn’t have spearate context – no risk of deadlock, no need to use ConfigureAwait(false)
We can use ConfigureAwait(false) to avoid capturing the context. Rule of thumb — always use it unless you are sure that you need a context.
Each method can have its own context.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 39
![Page 40: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/40.jpg)
SynchronizationContextSpecific threadexecuting the code
Delegates executed serially
Delegates executed in order
Send is synchronous
Post is asynchronous
Default (Thread Pool based)
No – any thread in the thread pool
No No Yes Yes
ASP.NET No – any thread in the thread pool
Yes No Yes No
WinForms Yes – UI thread Yes Yes Only if called on the UI thread
Yes
WPF Yes – UI thread Yes Yes Only if called on the UI thread
Yes
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 40
In ASP.NET only one continuation can be executed at a time for given request - no concurrency.In ASP.NET Core multiple continuations can run concurrently – we have concurrency and parallelism.
![Page 41: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/41.jpg)
State machine
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 41
![Page 42: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/42.jpg)
State machine 1 – before compilationOurAsyncMethod has four parts
◦ First part will run synchronously as the Task.FromResult is already resolved
◦ Second part will eventually block because of the delay
◦ Third part will block and explicitly create continuation
◦ Fourth part with just throw exception
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 42
![Page 43: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/43.jpg)
State machine 2 – method after compilation
async is no longer there — it is only on C# level.
DebuggerStepThroughAttribute tells the debugger to step through (ignore) the method.
Program.<OurAsyncMethod>d__1 <OurAsyncMethod>d__type created to encapsulate state machine pieces.
State is initialized to -1 meaning „ready to do some work”.
AsyncTaskMethodBuilder is a .NET class capable of executing the state machine.
Effectively we prepare the machine, start it and return the Task object with the result.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 44
![Page 44: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/44.jpg)
State machine 3 – fieldsProgram.<OurAsyncMethod>d__1 <OurAsyncMethod>d__type created to encapsulate state machine pieces.
<OurAsyncMethod>d__.<>1__state variable maintains the state
◦ Initially it is set to -1 meaning „not started”
◦ -2 means „done”
◦ Non-negative states indicate different pieces of the state machine
Three different awaiters as we have await three times in the original method.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 45
![Page 45: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/45.jpg)
State machine 4 – Start method
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 46
![Page 46: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/46.jpg)
State machine 5 – exception handlingWe capture the state to local variable.
We handle all exceptions and terminate the machine if needed.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 47
![Page 47: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/47.jpg)
State machine 6 – statesAwaiter at the beginning for different result types.
Four different branches as we have awaitthree times generating four blocks.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 48
![Page 48: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/48.jpg)
State machine 7 – num = -1, state = -1await Task.FromResult(false);
It starts in default.
We print to the console and get awaiter for the result.
If it is completed (as this is the case now)
◦ We call GetResult which returns the value immediately
◦ We end in line 72
◦ num = -1, state = -1
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 49
![Page 49: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/49.jpg)
State machine 8 – num = -1, state = -1await Task.Delay(200);
It starts in line 73.
We print to the console and get the awaiter.
If it is not completed◦ We change the state (so we
know where to come back)
◦ We call AwaitUnsafeOnCompleted(see in a bit)
◦ And then we return
Later we continue in case 1◦ We jump to the label IL_FA
and end in line 84
◦ num = 1, state = -1
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 50
![Page 50: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/50.jpg)
AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 51
![Page 51: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/51.jpg)
AwaitUnsafeOnCompleted —GetCompletionAction
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 52
![Page 52: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/52.jpg)
MoveNextRunner
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 53
![Page 53: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/53.jpg)
TaskAwaiter.AwaitUnsafeOnCompleted
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 54
![Page 54: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/54.jpg)
Task.SetContinuationForAwait
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 55
![Page 55: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/55.jpg)
SynchronizationContextAwaitTaskContinuation.Run
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 56
![Page 56: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/56.jpg)
SynchronizationContextAwaitTaskContinuation.PostAction
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 57
![Page 57: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/57.jpg)
State machine 9 – num = 1, state = -1await Task.Yield();
We start in line 85
We print to the console, get awaiter and check if it is completed.
It is not:◦ We set the state◦ Wait for the task◦ And return
Later we continue in case 2, jump to the label IL_168 and end in line 96.
num = 2, state = -1
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 58
![Page 58: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/58.jpg)
YieldAwaiter.AwaitUnsafeOnCompleted
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 59
![Page 59: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/59.jpg)
State machine 10 – num = 2, state = -1After last await
We start in line 97.
This part had no await in it so we just execute the code.
We print to the console and throw the exception.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 60
![Page 60: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/60.jpg)
State machine in Debug vs ReleaseDEBUG = CLASS
// Token: 0x02000003 RID: 3[CompilerGenerated]private sealed class <OurAsyncMethod>d__1
RELEASE = STRUCT
// Token: 0x02000003 RID: 3[CompilerGenerated][StructLayout(LayoutKind.Auto)]private struct <OurAsyncMethod>d__1
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 61
![Page 61: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/61.jpg)
Task vs voidASYNC TASK ASYNC VOID
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 62
They capture the context in the same way.
If exception is thrown in async Task, it is then remembered in the context of Task object and propagated when awaited or cleaned up.
In async void methods the exception is propagated immediately. This results in throwing unhandled exception on the thread pool which kills the application.
![Page 62: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/62.jpg)
Deadlocks
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 63
![Page 63: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/63.jpg)
DeadlocksBecause there is no thread we can cause a deadlock with just one thread!
Depending on the application type our code may run correctly or not.
Some popular problem examples:◦ Waiting on asynchronous code in synchronous one.
◦ Changing the synchronization context with continuations already scheduled.
◦ Synchronizing the execution between threads.
◦ Running continuation synchronously instead of through the synchronization context.
Use async all the way up!
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 64
![Page 64: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/64.jpg)
SynchronizationContextasync void DownloadButton_Click(object sender, EventArgs e){
// We are on the UI thread but we don’t block itawait ProcessDataAsync();
// We are back on the UI threadresultTextBox.Text = "Done";
}
async Task ProcessDataAsync(){
// We are still on the UI threadvar content = await DownloadAsync().ConfigureAwait(false);
// Because of ConfigureAwait we are *not* on the UI thread but on the thread pool// The second call to ConfigureAwait(false) is not requiredawait TransformAsync(content).ConfigureAwait(false);
}
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 65
![Page 65: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/65.jpg)
Console
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 66
![Page 66: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/66.jpg)
GUI
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 67
![Page 67: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/67.jpg)
GUI
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 68
![Page 68: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/68.jpg)
GUI
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 69
![Page 69: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/69.jpg)
GUI
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 70
![Page 70: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/70.jpg)
Unit test
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 71
![Page 71: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/71.jpg)
Unit test
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 72
![Page 72: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/72.jpg)
TaskCompletionSourceBy default TaskCompletionSource runs continuations synchronously when setting the result.
Continuations work differently for async and ContinueWith:◦ For await they run synchronously (almost always).
◦ For ContinueWith they run asynchronously (almost always).
We can modify TaskCompletionSource behavior by passing continuation creation flags.
As a workaround we can explicitly force the application to yield the continuation.
Use TaskCreationOptions.RunContinuationsAsynchronously where possible.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 73
![Page 73: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/73.jpg)
TaskCompletionSource
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 74
![Page 74: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/74.jpg)
Exception handling
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 75
![Page 75: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/75.jpg)
Unhandled exceptions on other threadsUnhandled exception on a normal thread kills the application.
We can add handler for the exception but we cannot swallow it.
This is very dangerous when loading code dynamically or using plugins.
What can we do about that?
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 76
![Page 76: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/76.jpg)
Hijacking thread creation
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 77
![Page 77: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/77.jpg)
Stacking threads AggregateExceptionTask can create a child. It can be either attached or detached.
Attached child task is coupled:◦ Parent waits for it
◦ Parent propagates its exceptions.
Task can block others from attaching by specifying DenyChildAttach. In that case child executes normally when trying to attach to the parent.
Contains all InnerExceptions – if a Task has child task, the exceptions create a tree (instead of a list).
Has method Flatten which makes a list from the exception tree.
Even if only one exception is thrown, it is still wrapped.
Can be retrieved by waiting for the task or by checking its Exception property.
Contains method Handle which takes care of rethrowing exception if it is not of a correct type.
Works weird for await code.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 78
![Page 78: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/78.jpg)
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 79
![Page 79: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/79.jpg)
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 80
![Page 80: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/80.jpg)
await vs Wait() vs GetAwaiter().GetResult()await works asynchronously.
await uses GetAwaiter() under the hood.
Wait() and GetAwaiter().GetResult() works synchronously.
Wait() doesn’t change the stacktrace:◦ This results in showing a lot more physical threading details.
GetAwaiter().GetResult() modifies the stacktrace:◦ This shows user code more.
Wait() wraps exceptions in AggregateException.
GetAwaiter().GetResult() and await do not wrap the exception.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 81
![Page 81: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/81.jpg)
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 82
![Page 82: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/82.jpg)
Exceptions in asyncVOID METHOD
One cannot await void method (sure?).
Exception is propagated but it is not deterministic.
Use AsyncContext from AsyncEx library.
TASK METHOD
Exception is stored in the Task.
You can also await the method and have the exception propagated.
When chaining in parent-child hierarchy (TaskCreationOptions.AttachedToParent) we may miss exceptions, even in AggregatedException.
If there is an unobserved exception, it is raised by finalizer thread in UnobservedTaskExceptionevent where it can be cleared. If not cleared, the process dies (.NET 4) or the exception is suppressed (.NET 4.5).
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 83
![Page 83: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/83.jpg)
Exceptions in async
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 84
![Page 84: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/84.jpg)
Exceptions in async
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 85
![Page 85: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/85.jpg)
Exceptions in async
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 86
![Page 86: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/86.jpg)
UnobservedTaskException
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 87
![Page 87: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/87.jpg)
Awaiting async voidWe cannot do it directly as method returns nothing.
We need to implement custom synchronization context.
To handle exceptions we need to write custom task scheduler.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 88
![Page 88: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/88.jpg)
await async void
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 89
![Page 89: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/89.jpg)
Catch exceptions in async void
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 90
![Page 90: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/90.jpg)
SummaryKnow your synchronization context — and don’t abuse it!
Do not use async void methods if you don’t have to.
Have async all the way up.
Don’t wait for asynchronous methods in synchronous code if you don’t have to.
Avoid creating threads if you can.
Always await tasks, handle all the exceptions.
Always add handlers to unobserved exceptions and unhandled exceptions.
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 91
![Page 91: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/91.jpg)
Q&A
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 92
![Page 92: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/92.jpg)
ReferencesJeffrey Richter - „CLR via C#”
Jeffrey Richter, Christophe Nasarre - „Windows via C/C++”
Mark Russinovich, David A. Solomon, Alex Ionescu - „Windows Internals”
Penny Orwick – „Developing drivers with the Microsoft Windows Driver Foundation”
Mario Hewardt, Daniel Pravat - „Advanced Windows Debugging”
Mario Hewardt - „Advanced .NET Debugging”
Steven Pratschner - „Customizing the Microsoft .NET Framework Common Language Runtime”
Serge Lidin - „Expert .NET 2.0 IL Assembler”
Joel Pobar, Ted Neward — „Shared Source CLI 2.0 Internals”
Adam Furmanek – „.NET Internals Cookbook”
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/README.md — „Book of the Runtime”
https://blogs.msdn.microsoft.com/oldnewthing/ — Raymond Chen „The Old New Thing”
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 93
![Page 93: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/93.jpg)
Referenceshttps://blog.adamfurmanek.pl/blog/2016/10/08/async-wandering-part-1/ — async in unit tests
https://blog.adamfurmanek.pl/blog/2017/01/07/async-wandering-part-3/ — WinForms
https://blog.adamfurmanek.pl/blog/2017/06/03/capturing-thread-creation-to-catch-exceptions/ — overriding Thread constructor to handle exceptions
https://blog.adamfurmanek.pl/blog/2017/01/14/async-wandering-part-4-awaiting-for-void-methods/ — awaiting async void
https://blog.adamfurmanek.pl/blog/2018/10/06/async-wandering-part-5-catching-exceptions-from-async-void/ — catching exceptions in async void
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 94
![Page 94: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/94.jpg)
Referenceshttps://www.codeproject.com/Articles/662735/Internals-of-Windows-Thread - Windows threads
http://aviadezra.blogspot.com/2009/06/net-clr-thread-pool-work.html - .NET ThreadPool
https://mattwarren.org/2017/04/13/The-CLR-Thread-Pool-Thread-Injection-Algorithm/ — ThreadPool injection algorithm
http://www.microsoft.com/download/en/details.aspx?id=19957 — TAP
https://msdn.microsoft.com/en-us/magazine/gg598924.aspx?f=255&MSPPError=-2147217396 – It’s all about the synchronization context
https://blogs.msdn.microsoft.com/seteplia/2018/10/01/the-danger-of-taskcompletionsourcet-class/ - TaskCompletionSource
https://blog.stephencleary.com/2014/04/a-tour-of-task-part-0-overview.html - Task internals
https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html — ASP.NET Core sychronization context
https://blogs.msdn.microsoft.com/pfxteam/2012/06/15/executioncontext-vs-synchronizationcontext/ — ExecutionContext internals
https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-3-runtime-context — ExecutionContext internals
https://blogs.msdn.microsoft.com/seteplia/2017/11/30/dissecting-the-async-methods-in-c/ - State machine
https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-1-compilation - State machine
27.05.2019 95ASYNC WANDERING, ADAM FURMANEK
![Page 95: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/95.jpg)
And….Last but not least– don’t forget to evaluate this session in the DevSum app!
#DevSum19
![Page 96: Async internals in - DevSum · Before .NET 4.5 LogicalCallContext was performing shadow copies and couldn’t be used between asynchronous points of invocation. Starting in .NET 4.6](https://reader034.fdocuments.us/reader034/viewer/2022050300/5f69b6e52417c56ec32df268/html5/thumbnails/96.jpg)
HT TP://BLOG.ADAMFURMANEK.PL
@FURMANEKADAM
27.05.2019 ASYNC WANDERING, ADAM FURMANEK 97