一尘不染

同步等待异步操作,为什么Wait()在这里冻结程序

c#

前言 :我在寻找一个解释,而不仅仅是一个解决方案。我已经知道了解决方案。

尽管花了几天时间研究有关基于任务的异步模式(TAP),异步和等待的MSDN文章,但我对某些更详细的信息仍然感到困惑。

我正在为Windows Store
Apps编写记录器,并且希望同时支持异步和同步记录。异步方法遵循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);
}

现在对应的同步方法…

版本1

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.Wait();
}

看起来正确,但是不起作用。整个程序永久冻结。

版本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.

版本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.

版本4(解决方案):

private void WriteToLog(string text)
{
    var task = Task.Run(async () => { await WriteToLogAsync(text); });
    task.Wait();
}

这可行。因此,2和3是错误的工具。但是1?1有什么问题,与4有什么区别?是什么导致1冻结?任务对象有问题吗?有没有明显的僵局?


阅读 365

收藏
2020-05-19

共1个答案

一尘不染

await您的异步方法内试图回到UI线程。

由于UI线程正忙于等待整个任务完成,因此出现了死锁。

移动异步调用即可Task.Run()解决此问题。
由于异步调用现在正在线程池线程上运行,因此它不会尝试返回UI线程,因此一切正常。

或者,您可以StartAsTask().ConfigureAwait(false)在等待内部操作使之返回线程池而不是UI线程之前进行调用,从而完全避免了死锁。

2020-05-19