我从Main调用了一个带有.Wait()的system.Threading.Tasks.Task方法。该方法最后有一个return语句,我希望表示该任务已完成",允许Main继续执行。然而,返回语句被命中(使用断点调试)但是执行不会在Main中继续,而是似乎只是"挂起",没有进一步执行。
代码在这里。
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Dfareporting.v1_3;
using Google.Apis.Dfareporting.v1_3.Data;
using _file = Google.Apis.Dfareporting.v1_3.Data.File;
using Google.Apis.Download;
using Google.Apis.Services;
using Google.Apis.Util.Store;
namespace DCMReportRetriever
{
public class DCMReportRetriever
{
public static void Main()
{
new DCMReportRetriever().Run().Wait();
return; // This statement is never executed
}
private async System.Threading.Tasks.Task Run()
{
...
foreach (_file f in files)
{
if (f.Status == "REPORT_AVAILABLE" && f.LastModifiedTime >= startDateSinceEpoch)
{
using (var stream = new FileStream(f.FileName + ".csv", FileMode.Append))
{
new MediaDownloader(service).Download(f.Urls.ApiUrl, stream);
}
}
}
return; // This statement is hit (debugged with breakpoint)
}
}
}
编辑:我应该补充一点,Main是我的入口点,显然是静态的async void主要不好?
答案 0 :(得分:1)
这是异常/等待状态机通过上下文切换导致死锁的常见错误。
你可以在这里找到一个清楚的解释:
Don't Block on Async Code(Stephen Cleary)
导致死锁的原因 情况就是这样:请记住我的介绍帖,等待任务后,当方法继续时,它将继续在上下文中。
在第一种情况下,此上下文是UI上下文(适用于除控制台应用程序之外的任何UI)。在第二种情况下,此上下文是ASP.NET请求上下文。
另一个重点:ASP.NET请求上下文不依赖于特定线程(如UI上下文),但它一次只允许一个线程。这个有趣的方面在AFAIK的任何地方都没有正式记录,但我的MSDN文章中提到了有关SynchronizationContext的内容。
所以这就是发生的事情,从顶级方法开始(用于ASP.NET的UI / MyController.Get的Button1_Click):
- 顶级方法调用GetJsonAsync(在UI / ASP.NET上下文中)。
- GetJsonAsync通过调用HttpClient.GetStringAsync(仍然在上下文中)启动REST请求。
- GetStringAsync返回未完成的任务,表示REST请求未完成。
- GetJsonAsync等待GetStringAsync返回的任务。捕获上下文,稍后将用于继续运行GetJsonAsync方法。 GetJsonAsync返回未完成的Task,表示GetJsonAsync方法未完成。
- 顶级方法同步阻止GetJsonAsync返回的任务。这会阻止上下文线程。
- ...最终,REST请求将完成。这样就完成了GetStringAsync返回的任务。
- GetJsonAsync的延续现在已准备好运行,它等待上下文可用,以便它可以在上下文中执行。
- 死锁。顶级方法是阻塞上下文线程,等待GetJsonAsync完成,GetJsonAsync正在等待上下文空闲,以便它可以完成。
醇>对于UI示例,“context”是UI上下文;对于ASP.NET示例,“context”是ASP.NET请求上下文。这种类型的死锁可能会导致“上下文”。
防止死锁 避免这种情况有两种最佳实践(我的介绍中都包含这些内容):
- 在“库”异步方法中,尽可能使用ConfigureAwait(false)。
- 不要阻止任务;一直使用async。
切换此代码:
new DCMReportRetriever().Run().Wait();
async analog:
await new DCMReportRetriever().Run();
甚至是这样:
await new DCMReportRetriever().Run().ConfigureAwait(false);
但是,.NET Framework不允许Main
方法异步,因此您需要在应用程序中添加“代理”方法,如下所示:
public static void Main()
{
MyMethodAsync().Wait();
}
static async Task MyMethodAsync()
{
await new DCMReportRetriever().Run().ConfigureAwait(continueOnCapturedContext: false);
}
private async Task Run()
{
...
foreach (_file f in files)
{
if (f.Status == "REPORT_AVAILABLE" && f.LastModifiedTime >= startDateSinceEpoch)
{
using (var stream = new FileStream(f.FileName + ".csv", FileMode.Append))
{
new MediaDownloader(service).Download(f.Urls.ApiUrl, stream);
}
}
}
}
您可以在本文中找到更多解释: