Seb*_*zus 301 .net c# task-parallel-library async-await windows-store-apps
前言:我正在寻找解释,而不仅仅是解决方案.我已经知道了解决方案.
尽管花了几天时间研究MSDN关于基于任务的异步模式(TAP),异步和等待的文章,但我仍然对一些更精细的细节感到困惑.
我正在为Windows Store应用程序编写一个记录器,我想支持异步和同步日志记录.异步方法遵循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);
}
Run Code Online (Sandbox Code Playgroud)
现在相应的同步方法......
版本1:
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
Run Code Online (Sandbox Code Playgroud)
这看起来是正确的,但它不起作用.整个程序永远冻结.
版本2:
嗯..也许任务没有开始?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
Run Code Online (Sandbox Code Playgroud)
这引发了 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();
}
Run Code Online (Sandbox Code Playgroud)
这引发了 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();
}
Run Code Online (Sandbox Code Playgroud)
这有效.所以,2和3是错误的工具.但1?1有什么不对,4有什么不同?1导致冻结的原因是什么?任务对象有问题吗?是否存在非明显的死锁?
SLa*_*aks 178
在await您的异步方法内试图回到UI线程.
由于UI线程忙于等待整个任务完成,因此您遇到了死锁.
移动异步调用以Task.Run()解决问题.
因为异步调用现在在线程池线程上运行,所以它不会尝试返回到UI线程,因此一切正常.
或者,您可以StartAsTask().ConfigureAwait(false)在等待内部操作之前调用它以使其返回到线程池而不是UI线程,从而完全避免死锁.
Ste*_*ary 47
async从同步代码调用代码可能非常棘手.
我在博客上解释了这种僵局的全部原因.简而言之,默认情况下会在每个开头保存一个"上下文",await用于恢复该方法.
因此,如果在UI上下文中调用await此async方法,则在完成时,该方法会尝试重新输入该上下文以继续执行.不幸的是,使用Wait(或Result)的代码将阻止该上下文中的线程,因此该async方法无法完成.
避免这种情况的指导原则是:
ConfigureAwait(continueOnCapturedContext: false)尽可能多地.这使您的async方法可以继续执行而无需重新输入上下文.async.使用await而不是Result或Wait.如果您的方法是自然异步的,那么您(可能)不应该公开同步包装器.
这是我所做的
private void myEvent_Handler(object sender, SomeEvent e)
{
// I dont know how many times this event will fire
Task t = new Task(() =>
{
if (something == true)
{
DoSomething(e);
}
});
t.RunSynchronously();
}
Run Code Online (Sandbox Code Playgroud)
工作正常,不阻塞UI线程