考虑以下代码:
public async Task SomeMethodAsync(){
//1. code here executes on the original context
//for simplicity sake, this doesn't complete instantly
var result1 = await Method1Async().ConfigureAwait(false);
//2. code here doesn't executes in the original context
现在我的问题是,如果在上面的方法中有另一个异步方法的调用:
//again for simplicity sake, this doesn't complete instantly
var result2 = await Method2();
//3. code in question is here
}
有问题的代码是在原始上下文中运行还是在另一个上下文中运行(可能是来自线程池或其他上下文的线程)?
另外,如果使用ConfigureAwait(false)调用method2,那么相关代码是否会在与段号2相同的上下文中运行?
答案 0 :(得分:7)
//for simplicity sake, this doesn't complete instantly
var result1 = await Method1Async().ConfigureAwait(false);
//again for simplicity sake, this doesn't complete instantly
var result2 = await Method2();
//code in question is here
假设你的异步方法没有立即完成,这里有两个简短的答案。
有问题的代码是在原始上下文中运行还是在另一个上下文中运行(可能是来自线程池或其他上下文的线程)?
有问题的代码将具有空SynchronizationContext.Current
值,因此将以默认SynchronizationContext
运行。
关于相关代码运行的线程:SynchronizationContext
做出决定。代码被发布/发送到SynchronizationContext
,SynchronizationContext
又将操作转发到特定的计算资源,例如特定的线程,特定的CPU核心或其他东西。在默认SynchronizationContext
的情况下,线程的选择取决于托管应用程序的内容(例如,控制台应用程序与ASP.NET应用程序)。在非默认ConfigureAwait(false)
的情况下,计算资源的选择取决于实施者的心血来潮:它可以在网络共享上运行。
另外,如果使用ConfigureAwait(false)调用method2,那么相关代码是否会在与段号2相同的上下文中运行?
如果method2有SynchronizationContext
,那么相关代码也会以默认false
运行。换句话说,当我们使用SynchronizationContext
时,任务不再尝试在捕获的上下文中继续。
这是一个可以回答你们两个问题的实验(the full listing is here)。
该实验使用维护简单string State
的{{1}},在Post
期间将其自身重置为当前上下文,并覆盖ToString()
以输出其State
值。
public class MySyncContext : SynchronizationContext
{
public string State { get; set; }
public override void Post(SendOrPostCallback callback, object state)
{
base.Post(s => {
SynchronizationContext.SetSynchronizationContext(this);
callback(s);
}, state);
}
public override string ToString() => State;
}
它是什么让我们看看代码是否在原始上下文中运行。
所以,让我们回忆一下你问的问题:
有问题的代码是在原始上下文中运行还是在另一个上下文中运行(可能是来自线程池或其他上下文的线程)?
要回答这个问题,我们有一个接近您的设置的实验。它首先将原始SynchronizationContext
设置为已知状态,然后等待两个异步方法,在第一个方法上使用ConfigureAwait(false)
,记录当前的SynchronizationContext
。
static async Task Run()
{
var syncContext = new MySyncContext { State = "The Original Context" };
SynchronizationContext.SetSynchronizationContext(syncContext);
Console.WriteLine("Before:" + SynchronizationContext.Current);
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine("After Result1:" + SynchronizationContext.Current);
await Task.Delay(1000);
Console.WriteLine("After Result2:" + SynchronizationContext.Current);
}
您想知道在第二种方法之后运行的代码是否会在原始上下文中运行。输出答案。第一种和第二种异步方法都没有将它们的延续发布到原始上下文中。
上面的代码ConfigureAwait(false)
输出:
Before:The Original Context
After Result1:
After Result2:
如果我们将上面的代码更改为ConfigureAwait(true)
,则两个方法都在原始上下文中运行它们的连续,输出为:
Before:The Original Context
After Result1:The Original Context
After Result2:The Original Context
所以你有它。通过true
和false
的各种组合运行the full code listing,使用多个不同的SynchronizationContext
值,延迟为0,看看会发生什么,这对我很有启发。
还值得阅读What does SynchronizationContext do?和It's All About the SynchronizationContext
的部分内容答案 1 :(得分:0)
ConfigureAwait(false)基本上抛弃了与Task关联的上下文。因此,当该任务调用其继续时,延续也将没有上下文。由于第二个异步方法是从第一个异步方法的延续中调用的,因此它的任务也没有上下文。在没有上下文的任务上调用ConfigureAwait(false)无效。
答案 2 :(得分:0)
如果在方法中的某个位置使用ConfigureAwait,建议在方法中使用每个Await。原因是只有在等待不完整的任务时才会捕获上下文。如果任务已经完成,则不会捕获上下文。还要记住,硬件和网络之类的东西在执行任务所需的时间中起作用。
第一个问题的答案取决于这些事情。如果Method1Async()在没有等待的情况下完成,则代码为no。 2仍然可以在原始上下文中执行,因此代码为no。 3
等待提供Method1Async(),它设法从原始上下文中分离。该方法的其余部分将在线程池上下文中执行,同时在同一线程池上下文中调用method2。回答你的第二个问题是肯定的。