ConfigureAwait的使用
Task.ConfigureAwait(bool)
指示了Awaiter完成任务时是否尝试将结果返回原上下文中继续执行,Ture
为尝试返回,False
则相反。
在讨论ConfigureAwait
的正确使用场景之前,则应该先了解SynchronizationContext
类型。
SynchronizationContext
SynchronizationContext
提供了在同步模型中传播上下文的基本功能,其中Send()
方法将一个同步消息调度到一个上下文中,Post()
方法将一个异步委托调度到一个上下文中。
例如,WinForms具有一个SynchronizationContext
的派生类型,它重写了Post
方法,以完成相当于Control.BeginInvoke
的功能;这意味着对它的Post
方法的任何调用都将导致委托稍后在该Control的UI线程上被调用。因此,想要在WinForms 的UI线程上正确执行一个委托方法,只需获得该UI线程的SynchronizationContext
实例,将其作为Post
的参数即可。
WPF也是如此,它有自己的SynchronizationContext
派生类型,它重写了Post
方法,同样可以将一个委托调度到UI线程上(通过Dispatcher.BeginInvoke
)。
待补充例子
ConfigureAwait
- 默认情况下,
Task.ConfigureAwait(true)
即默认调度回原上下文,排队回调可能涉及额外的分配工作,即更多的资源损耗。 - 什么情况适合使用
Task.ConfigureAwait(false)
- 在不需要回到原始上下文中调用回调的时候可以使用;但类似WPF中需要在原有的上下文中(UI线程)修改界面的情况下,不应使用
- 更高的性能,避免了不必要的队列,避免了不必要的线程访问
一般来说,编写通用库时可以使用Task.ConfigureAwait(false)
,因为通用库一般不在乎使用环境,即他不会做任何需要以特定方式与应用模型发生交互的事情。而在编写应用程序时,则更多的使用默认的Task.ConfigureAwait(true)
,比如WinForms、WPF、ASP.NET Core等。
DeadLock
如下文例子所示,错误的使用同步上下文调度器将导致死锁问题发生,MaxConcurrencySynchronizationContext
是一个只有一个线程可以使用的同步上下文调度器。我们调用了Method2
并阻塞它等待操作完成,Method2
中调用了Method1
方法,在控制台打印了相关信息后,await Task.Delay
将会捕获当前的SynchronizationContext
,并在操作完成后将回调返回至SynchronizationContext
中排队执行。由于并发量限制了1且我们的代码已经阻塞了这个线程,死锁情况发生。
感谢黑洞视界大佬分享的知识。
void Main()
{
Task.Run(() =>
{
var sc = new MaxConcurrencySynchronizationContext(1);
SynchronizationContext.SetSynchronizationContext(sc);
sc.Send( _ => { Method2(); }, null);
});
}
async Task Method1()
{
Console.WriteLine($"Start:{nameof(Method1)}");
await Task.Delay(100);
Console.WriteLine($"End:{nameof(Method1)}");
}
void Method2()
{
Console.WriteLine($"Start:{nameof(Method2)}");
Method1().GetAwaiter().GetResult();
Console.WriteLine($"End:{nameof(Method2)}");
}
internal sealed class MaxConcurrencySynchronizationContext : SynchronizationContext
{
private readonly SemaphoreSlim _semaphore;
public MaxConcurrencySynchronizationContext(int maxConcurrencyLevel)
{
_semaphore = new SemaphoreSlim(maxConcurrencyLevel);
}
public override void Post(SendOrPostCallback d, object state)
{
_semaphore.WaitAsync().ContinueWith(delegate
{
try
{
d(state);
}
finally
{
_semaphore.Release();
}
}, default, TaskContinuationOptions.None, TaskScheduler.Default);
}
public override void Send(SendOrPostCallback d, object state)
{
_semaphore.Wait();
try
{
d(state);
}
finally
{
_semaphore.Release();
}
}
}