Preface :我正在寻找解释,而不仅仅是解决方案 . 我已经知道了解决方案 .
尽管花了几天时间研究MSDN关于基于任务的异步模式(TAP),异步和等待的文章,但我仍然对一些更精细的细节感到困惑 .
我正在为Windows Store应用程序编写 Logger ,我想支持异步和同步日志记录 . 异步方法遵循TAP,同步方法应该隐藏所有这些,并且看起来像普通方法一样工作 .
这是异步日志记录的核心方法:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
现在相应的同步方法......
Version 1 :
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
这看起来是正确的,但它不起作用 . 整个程序永远冻结 .
Version 2 :
嗯..也许任务没有开始?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
抛出 InvalidOperationException: Start may not be called on a promise-style task.
Version 3:
嗯.. Task.RunSynchronously
听起来很有希望 .
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
抛出 InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Version 4 (the solution):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
这很有效 . 所以,2和3是错误的工具 . 但1? 1有什么不对,4有什么不同? 1导致冻结的原因是什么?任务对象有问题吗?是否存在非明显的死锁?
请帮我理解 .
4 回答
异步方法中的
await
试图回到UI线程 .由于UI线程忙于等待整个任务完成,因此您遇到了死锁 .
将异步调用移至
Task.Run()
可解决此问题 .因为异步调用现在在线程池线程上运行,所以它不会尝试返回到UI线程,因此一切正常 .
或者,您可以在等待内部操作之前调用
StartAsTask().ConfigureAwait(false)
以使其返回到线程池而不是UI线程,从而完全避免死锁 .从同步代码调用
async
代码可能非常棘手 .我解释full reasons for this deadlock on my blog . 简而言之,默认情况下会在
await
的开头保存"context"并用于恢复该方法 .因此,如果在UI上下文中调用此方法,则
await
完成时,async
方法会尝试重新进入该上下文以继续执行 . 不幸的是,使用Wait
(或Result
)的代码将阻止该上下文中的线程,因此async
方法无法完成 .避免这种情况的指导原则是:
尽可能使用
ConfigureAwait(continueOnCapturedContext: false)
. 这使您的async
方法可以继续执行而无需重新输入上下文 .一直使用
async
. 使用await
而不是Result
或Wait
.如果您的方法是自然异步的,那么you (probably) shouldn't expose a synchronous wrapper .
这就是我做的
工作得很好而且没有阻止UI线程
使用小型自定义同步上下文,同步功能可以等待异步功能的完成,而不会产生死锁 . 这是WinForms应用程序的一个小例子 .