c# - system.Threading.Tasks.Task 方法中的代码不是 "Finish"

标签 c# .net asynchronous async-await

我从 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 是我的入口点,显然 static async void Main 不好?

最佳答案

这是通过异步/等待状态机进行上下文切换而导致死锁的常见错误。

您可以在这里找到清晰的解释:

Don't Block on Async Code (斯蒂芬·克利里)

What Causes the Deadlock Here’s the situation: remember from my intro post that after you await a Task, when the method continues it will continue in a context.

In the first case, this context is a UI context (which applies to any UI except Console applications). In the second case, this context is an ASP.NET request context.

One other important point: an ASP.NET request context is not tied to a specific thread (like the UI context is), but it does only allow one thread in at a time. This interesting aspect is not officially documented anywhere AFAIK, but it is mentioned in my MSDN article about SynchronizationContext.

So this is what happens, starting with the top-level method (Button1_Click for UI / MyController.Get for ASP.NET):

  1. The top-level method calls GetJsonAsync (within the UI/ASP.NET context).
  2. GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).
  3. GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
  4. GetJsonAsync awaits the Task returned by GetStringAsync. The context is captured and will be used to continue running the GetJsonAsync method later. GetJsonAsync returns an uncompleted Task, indicating that the GetJsonAsync method is not complete.
  5. The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
  6. … Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
  7. The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
  8. Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for the context to be free so it can complete.

For the UI example, the “context” is the UI context; for the ASP.NET example, the “context” is the ASP.NET request context. This type of deadlock can be caused for either “context”.

Preventing the Deadlock There are two best practices (both covered in my intro post) that avoid this situation:

  • In your “library” async methods, use ConfigureAwait(false) wherever possible.
  • Don’t block on Tasks; use async all the way down.

切换此代码:

new DCMReportRetriever().Run().Wait();

到异步模拟:

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);
            }
        }
    }
}

您可以在本文中找到更多说明:

Best Practices in Asynchronous Programming

关于c# - system.Threading.Tasks.Task 方法中的代码不是 "Finish",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27899045/

相关文章:

java - 向多个客户端发送文件数据?

c# - 调用 Task.Delay() 并在几分钟后询问还剩多少时间

C# 用数字对数组列表进行排序

c# - 升级到 MVC 5 的网站在 ControllerContext 上缺少 DisplayMode 属性

.net - File.Move 对于 UNC 路径无法正常工作

c# - 注册装饰器与注册 IEnumerable<T> 冲突

c# - 字节数组(Web Api 2)到 blob(Angular 客户端)

c# - 在泛型方法中仅设置第二个参数类型

c# - 将不同的数字类型作为参数发送到方法时是否存在性能问题?

c# - 我如何知道异步套接字读取何时结束?