c# - ConfigureAwait 将继续推送到池线程

标签 c# .net multithreading task-parallel-library async-await

这是一些 WinForms 代码:

async void Form1_Load(object sender, EventArgs e)
{
    // on the UI thread
    Debug.WriteLine(new { where = "before", 
        Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });

    var tcs = new TaskCompletionSource<bool>();

    this.BeginInvoke(new MethodInvoker(() => tcs.SetResult(true)));

    await tcs.Task.ContinueWith(t => { 
        // still on the UI thread
        Debug.WriteLine(new { where = "ContinueWith", 
            Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });
    }, TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(false);

    // on a pool thread
    Debug.WriteLine(new { where = "after", 
        Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });
}

输出:

{ where = before, ManagedThreadId = 10, IsThreadPoolThread = False }
{ where = ContinueWith, ManagedThreadId = 10, IsThreadPoolThread = False }
{ where = after, ManagedThreadId = 11, IsThreadPoolThread = True }

Why does ConfigureAwait pro-actively push the await continuation to a pool thread here?

I use "pushing to a pool thread" here to describe the case when the primary continuation callback (the action parameter to TaskAwaiter.UnsafeOnCompleted has been invoked on one thread, but the secondary callback (the one passed to ConfiguredTaskAwaiter.UnsafeOnCompleted) is queued to a pool thread.

The docs say:

continueOnCapturedContext ... true to attempt to marshal the continuation back to the original context captured; otherwise, false.

I understand there's WinFormsSynchronizationContext installed on the current thread. Still, there is no attempt to marshal to be made, the execution point is already there.

Thus, it's more like "never continue on the original context captured"...

As expected, there's no thread switch if the execution point is already on a pool thread without a synchronization context:

await Task.Delay(100).ContinueWith(t => 
{ 
    // on a pool thread
    Debug.WriteLine(new { where = "ContinueWith", 
        Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });
}, TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(false);
{ where = before, ManagedThreadId = 10, IsThreadPoolThread = False }
{ where = ContinueWith, ManagedThreadId = 6, IsThreadPoolThread = True }
{ where = after, ManagedThreadId = 6, IsThreadPoolThread = True }

Updated, one more test to see if any sync. context is not good enough for continuation (rather than the original one). This is indeed the case:

class DumbSyncContext: SynchronizationContext
{
}

// ...

Debug.WriteLine(new { where = "before", 
    Thread.CurrentThread.ManagedThreadId, 
    Thread.CurrentThread.IsThreadPoolThread });

var tcs = new TaskCompletionSource<bool>();

var thread = new Thread(() =>
{
    Debug.WriteLine(new { where = "new Thread",                 
        Thread.CurrentThread.ManagedThreadId,
        Thread.CurrentThread.IsThreadPoolThread});
    SynchronizationContext.SetSynchronizationContext(new DumbSyncContext());
    tcs.SetResult(true);
    Thread.Sleep(1000);
});
thread.Start();

await tcs.Task.ContinueWith(t => {
    Debug.WriteLine(new { where = "ContinueWith",
        Thread.CurrentThread.ManagedThreadId,
        Thread.CurrentThread.IsThreadPoolThread});
}, TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(false);

Debug.WriteLine(new { where = "after", 
    Thread.CurrentThread.ManagedThreadId, 
    Thread.CurrentThread.IsThreadPoolThread });
{ where = before, ManagedThreadId = 9, IsThreadPoolThread = False }
{ where = new Thread, ManagedThreadId = 10, IsThreadPoolThread = False }
{ where = ContinueWith, ManagedThreadId = 10, IsThreadPoolThread = False }
{ where = after, ManagedThreadId = 6, IsThreadPoolThread = True }

最佳答案

Why ConfigureAwait pro-actively pushes the await continuation to a pool thread here?

它并没有“将其推送到线程池线程”,而是说“不要强制自己回到之前的 SynchronizationContext”。

如果您不捕获现有上下文,那么处理 await 之后的代码的延续将只在线程池线程上运行,因为没有要编码回的上下文。

现在,这与“推送到线程池”略有不同,因为没有保证当您执行 ConfigureAwait(false) 时它会在线程池上运行。如果你打电话:

await FooAsync().ConfigureAwait(false);

FooAsync() 可能会同步执行,在这种情况下,您将永远不会离开当前上下文。在那种情况下,ConfigureAwait(false) 没有实际效果,因为 await 特性创建的状态机将短路并直接运行。

如果你想看到它的实际效果,可以像这样制作一个异步方法:

static Task FooAsync(bool runSync)
{
   if (!runSync)
       await Task.Delay(100);
}

如果你这样调用它:

await FooAsync(true).ConfigureAwait(false);

您会看到您停留在主线程上(前提是在等待之前是当前上下文),因为在代码路径中没有实际执行的异步代码。然而,与 FooAsync(false).ConfigureAwait(false); 的相同调用将导致它在执行后跳转到线程池线程。

关于c# - ConfigureAwait 将继续推送到池线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22672984/

相关文章:

c# - 如何制作伦敦地铁的加权图

.net - 随着时间的推移增加或减少值

multithreading - Qt GUI 用户与 QThread 对象中的 QMessageBox 交互

c# - 在字符串列表中搜索字符串的有效方法?

c# - 如何从 C++ 为 Xamarin.Android 创建 C# DLL

c# - 您使用哪个 .NET Memcached 客户端,EnyimMemcached 与 BeITMemcached?

.net - 使用 ODBC 和 C++ Net 将空 BLOB 插入 SQLite DB

.net - DotNetOpenAuth OpenIdLogin CSS 样式

c++ - OpenMP 并行峰值

vb.net - 如何与多个线程共享一个资源(一个串口)