近日在开发Avalonia应用的过程中,我遭遇了一个有关异步编程的棘手问题。当我在ViewModel中通过async/await模式调用异步方法加载数据时,发现当嵌套使用另一个异步操作时,预期的返回值并未如期到来,反而导致了线程的阻塞。
举个例子,在请求HTTP资源时:
HttpResponseMessage response = await httpClient.GetAsync(url);
如果上述方式出现问题,但用下面的同步方式可以正常得到响应:
HttpResponseMessage response = httpClient.GetAsync(url).Result;
为了解决这个问题,我开始查阅资料并在群内讨论,终于发现了ConfigureAwait属性,并决定对此问题做深入研究。
要了解Avalonia和其他基于.Net的图形用户界面框架中遇到的异步编程问题,首先要知道异步编程最大的威胁——死锁。下面这个示例会在GUI或ASP.NET环境中导致死锁:
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
public static void Test()
{
var delayTask = DelayAsync();
delayTask.Wait();
}
}
出现死锁的原因,源于异步操作默认处理机制。当await一个尚未完成的Task时,它会尝试捕获当前的“同步上下文”,并在Task完成时恢复执行。对GUI和ASP.NET应用来说,存在的SynchronizationContext在同一时间仅允许一个操作。如果GUI或ASP.NET应用尝试在同步代码中等待异步操作的完成,就会形成互相等待的情况,也就是死锁。
在异步方法中使用ConfigureAwait(false)可以有效避免这种情况:
public static async Task HandleClickAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
}
ConfigureAwait的这个用法,就是在让await不要恢复原来的同步上下文,它允许异步方法中剩余的代码在线程池的线程上运行,这不仅避免了死锁,也提高了执行效率。
必须强调的是,不是所有异步方法都应使用ConfigureAwait(false)。在GUI和ASP.NET应用中,任何涉及UI组件或依赖于特定同步上下文的操作都不能放弃上下文,否则可能会导致运行时错误。
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
try
{
await Task.Delay(1000);
}
finally
{
button1.Enabled = true;
}
}
但对于不与UI线程直接交互的逻辑代码,推荐使用ConfigureAwait(false)以提升性能与避免潜在的死锁隐患。
总结而言,对于.Net开发者,异步编程是一项关键技能,而理解并合理应用ConfigureAwait,无疑是提升程序稳定性、性能和可维护性的有效途径。在你的Avalonia或其他.Net应用中遇到异步操作相关的问题时,不妨考虑对ConfigureAwait这一属性的深入研究与应用。
其他更详细内容可以查看:
https://devblogs.microsoft.com/dotnet/configureawait-faq/