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
SynchronizationContextroughly 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:
OnCompletedflows context, andUnsafeOnCompleteddoes 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!