ConfigureAwait推动延续到池中的线程 [英] ConfigureAwait pushes the continuation to a pool thread

查看:288
本文介绍了ConfigureAwait推动延续到池中的线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

下面是一个WinForms code:

 异步无效Form1_Load的(对象发件人,EventArgs的)
{
    //在UI线程
    的Debug.WriteLine(新{其中,=前,
        Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread});

    VAR TCS =新TaskCompletionSource<布尔>();

    this.BeginInvoke(新MethodInvoker(()=> tcs.SetResult(真)));

    等待tcs.Task.ContinueWith(T => {
        //仍然在UI线程
        的Debug.WriteLine(新{其中,=ContinueWith
            Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread});
    },TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(假);

    //在一个线程池
    的Debug.WriteLine(新{其中,=后,
        Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread});
}
 

输出:

{=在哪里之前,ManagedThreadId = 10,IsThreadPoolThread = FALSE}
{其中,= ContinueWith,ManagedThreadId = 10,IsThreadPoolThread = FALSE}
{=的地方后,ManagedThreadId = 11,IsThreadPoolThread = TRUE}

为什么ConfigureAwait积极主动地推动计谋延续到池中的线程在这里?

的<一个href="http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.configureawait%28v=vs.110%29.aspx">MSDN文档说:

  

continueOnCapturedContext ...真正的尝试元帅   继续回到拍摄的原始上下文;否则为false。

据我所知有 WinFormsSynchronizationContext 安装在当前线程上。尽管如此,还有的没有试图元帅的制作,执行点已经存在。

因此​​,它更像的从来没有继续拍摄的原始上下文 ...

正如预期的那样,没有线程切换,如果执行点已经在一个池线程没有同步上下文:

 等待Task.Delay(100).ContinueWith(T =&GT;
{
    //在一个线程池
    的Debug.WriteLine(新{其中,=ContinueWith
        Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread});
},TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(假);
 

{=在哪里之前,ManagedThreadId = 10,IsThreadPoolThread = FALSE}
{其中,= ContinueWith,ManagedThreadId = 6,IsThreadPoolThread = TRUE}
{其中,=之后,ManagedThreadId = 6,IsThreadPoolThread = TRUE}

我要看看<一href="http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs#71e736efa1b4ccaa">implementation对 ConfiguredTaskAwaitable 的答案。

更新后,多了一个测试,看的任意的同步。上下文不用于继续(而不是原来的一个)不够好。这的确是这样的:

 类DumbSyncContext:的SynchronizationContext
{
}

// ...

的Debug.WriteLine(新{其中,=前,
    Thread.CurrentThread.ManagedThreadId,
    Thread.CurrentThread.IsThreadPoolThread});

VAR TCS =新TaskCompletionSource&LT;布尔&GT;();

VAR线程=新主题(()=&GT;
{
    的Debug.WriteLine(新{其中,=新主题
        Thread.CurrentThread.ManagedThreadId,
        Thread.CurrentThread.IsThreadPoolThread});
    SynchronizationContext.SetSynchronizationContext(新DumbSyncContext());
    tcs.SetResult(真正的);
    Thread.sleep代码(1000);
});
thread.Start();

等待tcs.Task.ContinueWith(T =&GT; {
    的Debug.WriteLine(新{其中,=ContinueWith
        Thread.CurrentThread.ManagedThreadId,
        Thread.CurrentThread.IsThreadPoolThread});
},TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(假);

的Debug.WriteLine(新{其中,=后,
    Thread.CurrentThread.ManagedThreadId,
    Thread.CurrentThread.IsThreadPoolThread});
 

{其中,前=,ManagedThreadId = 9,IsThreadPoolThread = FALSE}
{哪里=新主题,ManagedThreadId = 10,IsThreadPoolThread = FALSE}
{其中,= ContinueWith,ManagedThreadId = 10,IsThreadPoolThread = FALSE}
{其中,=之后,ManagedThreadId = 6,IsThreadPoolThread = TRUE}

解决方案
  

为什么ConfigureAwait积极主动地推动计谋继续在这里一个线程池?

它不会推到一个线程池线程不亚于说:不要强迫自己回来了previous 的SynchronizationContext

如果你没有捕捉到现有的情况下,则延续其处理后的code 等待将只是一个线程池中的线程上运行,而不是,因为没有上下文封送回。

现在,这是不是推到一个线程池微妙的不同,因为没有一个的保证的,它可以运行在一个当你做线程池 ConfigureAwait (假)。如果调用:

 等待FooAsync()ConfigureAwait(假)。
 

这可能是 FooAsync()将执行同步,在这种情况下,你将永远不会离开目前的情况下。在这种情况下, ConfigureAwait(假)有没有真正的影响,因为由计谋创建的状态机功能将短电路只需直接运行。

如果你想看到这个动作,使异步方法如下所示:

 静态任务FooAsync(布尔runSync)
{
   如果(!runSync)
       等待Task.Delay(100);
}
 

如果你调用此类似:

 等待FooAsync(真).ConfigureAwait(假);
 

您会看到,你留在主线程(只要是当前上下文之前的await)上,因为没有实际的异步code执行在code路径。与同一呼叫 FooAsync(假).ConfigureAwait(假); 将导致它不过跳转到线程池中的线程执行后,

Here is a WinForms code:

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 });
}

The output:

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

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

The MSDN 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 }

I'm about to look at the implementation of ConfiguredTaskAwaitable for the answers.

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?

It doesn't "push it to a thread pool thread" as much as say "don't force myself to come back to the previous SynchronizationContext".

If you don't capture the existing context, then the continuation which handles the code after that await will just run on a thread pool thread instead, since there is no context to marshal back into.

Now, this is subtly different than "push to a thread pool", since there isn't a guarantee that it will run on a thread pool when you do ConfigureAwait(false). If you call:

await FooAsync().ConfigureAwait(false);

It is possible that FooAsync() will execute synchronously, in which case, you will never leave the current context. In that case, ConfigureAwait(false) has no real effect, since the state machine created by the await feature will short circuit and just run directly.

If you want to see this in action, make an async method like so:

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

If you call this like:

await FooAsync(true).ConfigureAwait(false);

You'll see that you stay on the main thread (provided that was the current context prior to the await), since there is no actual async code executing in the code path. The same call with FooAsync(false).ConfigureAwait(false); will cause it to jump to thread pool thread after execution, however.

这篇关于ConfigureAwait推动延续到池中的线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆