I'm no expert at async despite having written C# for many years, but AFAICT after reading some MSDN blog posts:
- Awaitables (such as
Task
) may either capture or not capture the currentSynchronizationContext
. - A
SynchronizationContext
roughly corresponds to a thread: if I'm on the UI thread and callawait task
, which 'flows context', the continuation is run on the UI thread. If I callawait task.ConfigureAwait(false)
, the continuation is run on some random threadpool thread which may/may not be the UI thread. - For awaiters:
OnCompleted
flows context, andUnsafeOnCompleted
does not flow context.
OK, with that established, let's take a look at the code Roslyn generates for await Task.Yield()
. This:
using System;
using System.Threading.Tasks;
public class C {
public async void M() {
await Task.Yield();
}
}
Results in this compiler-generated code (you may verify yourself here):
public class C
{
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <M>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncVoidMethodBuilder <>t__builder;
private YieldAwaitable.YieldAwaiter <>u__1;
void IAsyncStateMachine.MoveNext()
{
int num = this.<>1__state;
try
{
YieldAwaitable.YieldAwaiter yieldAwaiter;
if (num != 0)
{
yieldAwaiter = Task.Yield().GetAwaiter();
if (!yieldAwaiter.IsCompleted)
{
num = (this.<>1__state = 0);
this.<>u__1 = yieldAwaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<YieldAwaitable.YieldAwaiter, C.<M>d__0>(ref yieldAwaiter, ref this);
return;
}
}
else
{
yieldAwaiter = this.<>u__1;
this.<>u__1 = default(YieldAwaitable.YieldAwaiter);
num = (this.<>1__state = -1);
}
yieldAwaiter.GetResult();
yieldAwaiter = default(YieldAwaitable.YieldAwaiter);
}
catch (Exception arg_6E_0)
{
Exception exception = arg_6E_0;
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult();
}
[DebuggerHidden]
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
this.<>t__builder.SetStateMachine(stateMachine);
}
}
[AsyncStateMachine(typeof(C.<M>d__0))]
public void M()
{
C.<M>d__0 <M>d__;
<M>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
<M>d__.<>1__state = -1;
AsyncVoidMethodBuilder <>t__builder = <M>d__.<>t__builder;
<>t__builder.Start<C.<M>d__0>(ref <M>d__);
}
}
Notice that AwaitUnsafeOnCompleted
is being called with the awaiter, instead of AwaitOnCompleted
. AwaitUnsafeOnCompleted
, in turn, calls UnsafeOnCompleted
on the awaiter. YieldAwaiter
does not flow the current context in UnsafeOnCompleted
.
This really confuses me because this question seems to imply that Task.Yield
does capture the current context; the asker is frustrated that at the lack of a version that doesn't. So I'm confused: does or doesn't Yield
capture the current context?
If it doesn't, how can I force it to? I'm calling this method on the UI thread, and I really need the continuation to run on the UI thread, too. YieldAwaitable
lacks a ConfigureAwait()
method, so I can't write await Task.Yield().ConfigureAwait(true)
.
Thanks!