【问题标题】:Code Does Not "Finish" in system.Threading.Tasks.Task Methodsystem.Threading.Tasks.Task 方法中的代码未“完成”
【发布时间】:2015-01-12 19:37:16
【问题描述】:

我使用 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 不好?

【问题讨论】:

  • 请显示调用Run的部分代码。
  • 首先,return 语句是多余的。第二:发布一个简短但完整的程序,可以重现问题。参考MVCE

标签: c# .net asynchronous async-await


【解决方案1】:

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

你可以在这里找到一个清楚的解释:

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

导致死锁的原因 情况如下:请记住,在我的介绍帖子中,在等待任务之后,当方法继续时,它将在上下文中继续。

在第一种情况下,此上下文是 UI 上下文(适用于除控制台应用程序之外的任何 UI)。在第二种情况下,此上下文是一个 ASP.NET 请求上下文。

另外一点很重要:ASP.NET 请求上下文不绑定到特定线程(就像 UI 上下文一样),但它一次只允许一个线程进入。这个有趣的方面在 AFAIK 的任何地方都没有正式记录,但在我关于 SynchronizationContext 的 MSDN 文章中提到了。

这就是发生的事情,从顶级方法开始(UI 的 Button1_Click / ASP.NET 的 MyController.Get):

  1. 顶级方法调用 GetJsonAsync(在 UI/ASP.NET 上下文中)。
  2. GetJsonAsync 通过调用 HttpClient.GetStringAsync 启动 REST 请求(仍在上下文中)。
  3. GetStringAsync 返回一个未完成的任务,表示 REST 请求未完成。
  4. GetJsonAsync 等待 GetStringAsync 返回的任务。上下文被捕获并将用于稍后继续运行 GetJsonAsync 方法。 GetJsonAsync 返回未完成的Task,表示GetJsonAsync 方法未完成。
  5. 顶级方法同步阻塞 GetJsonAsync 返回的任务。这会阻塞上下文线程。
  6. ... 最终,REST 请求将完成。这样就完成了 GetStringAsync 返回的任务。
  7. GetJsonAsync 的延续现已准备好运行,它等待上下文可用,以便可以在上下文中执行。
  8. 死锁。顶级方法正在阻塞上下文线程,等待 GetJsonAsync 完成,而 GetJsonAsync 正在等待上下文空闲以便它可以完成。

对于 UI 示例,“上下文”是 UI 上下文;对于 ASP.NET 示例,“上下文”是 ASP.NET 请求上下文。这种类型的死锁可能由任一“上下文”引起。

防止死锁 有两种最佳做法(都在我的介绍文章中介绍过)可以避免这种情况:

  • 在“库”异步方法中,尽可能使用 ConfigureAwait(false)。
  • 不要阻止任务;一直使用异步。

切换此代码:

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

【讨论】:

  • OP 说 return 语句在断点处被命中。如果这是真的,那么你的答案是无关紧要的。否则,OP 应该考虑这个答案。
  • @SriramSakthivel 第一个断点被命中,然后出现死锁。
  • 我不明白你的评论是什么意思。在 OP 的问题中命中带注释的 return 语句后,怎么会发生死锁?
  • @SriramSakthivel 请阅读我链接的完整文章。这是在异步方法上使用 Wait() 方法的常见场景,死锁发生在 .NET Core 内部的状态机中。
  • 我很久以前就读过那篇文章了。我不需要再读一遍。我建议你仔细阅读这个问题。 OP 说 return 语句被命中(在断点中)。如果这是真的,那么代码就不可能死锁。 OP 对该信息有误,或者您的答案无关紧要。编辑:FWIW 我不是反对者。
猜你喜欢
  • 1970-01-01
  • 2013-11-14
  • 1970-01-01
  • 2019-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
相关资源
最近更新 更多