【问题标题】:await not blocking until Task finishes在任务完成之前等待不阻塞
【发布时间】:2013-03-16 07:21:49
【问题描述】:

我试图用await ConsumerTask; 阻止RequestHandler.ParseAll(),但是当我在那里设置断点时,我总是首先得到“完成...”输出...然后Parse2() 失败并出现NullReferenceException。 (这是我的猜测:“GC 开始清理,因为 _handler 超出范围”)

无论如何,我无法弄清楚为什么会发生这种情况。

class MainClass
{
    public async void DoWork()
    {
        RequestHandler _handler = new RequestHandler();
        string[] mUrls;
        /* fill mUrls here with values */
        await Task.Run(() => _handler.ParseSpecific(mUrls));
        Console.WriteLine("Done...");
    }
}
static class Parser
{
    public static async Task<IEnumerable<string>> QueryWebPage(string url) { /*Query the url*/ }

    public static async Task Parse1(Query query) 
    { 
        Parallel.ForEach(/*Process data here*/);
    }

    public static async Task Parse2(Query query)
    {
        foreach(string line in query.WebPage)
            /* Here i get a NullReference exception because query.WebPage == null */
    }
}
sealed class RequestHandler
{
    private BlockingCollection<Query> Queue;
    private Task ConsumerTask = Task.Run(() => /* call consume() for each elem in the queue*/);

    private async void Consume(Query obj)
    {
        await (obj.BoolField ? Parser.Parse1(obj) : Parser.Parse2(obj));
    }

    public async void ParseSpecific(string[] urls)
    {
        foreach(string v in urls)
            Queue.Add(new Query(await QueryWebPage(v), BoolField: false));

        Queue.CompleteAdding();
        await ConsumerTask;
        await ParseAll(true);
    }

    private async Task ParseAll(bool onlySome)
    {
        ReInit();
        Parallel.ForEach(mCollection, v => Queue.Add(new Query(url, BoolField:false)));
        Queue.CompleteAdding();
        await ConsumerTask;
        /* Process stuff further */
    }
}
struct Query
{
    public readonly string[] WebPage;
    public readonly bool BoolField;
    public Query(uint e, IEnumerable<string> page, bool b) : this()
    {
        Webpage = page.ToArray();
        BoolField = b;
    }
}

【问题讨论】:

  • 您的示例非常复杂(对于有很多没有Async 后缀的异步方法更是如此)-您可以将其减少吗?
  • ParseSpecific 返回其第一个 await 语句。你不await ParseSpecific
  • @JonSkeet:我尽可能地减少它,没有试图留下任何重要的东西......但我会尝试。
  • @Nefarion:CodesInChaos 在他的评论中已经弄清楚了——我正在详细说明。
  • @CodesInChaos:我希望你不介意我基本上跳出你发现问题所在的事实 :) 很好,先生!

标签: c# asynchronous async-await


【解决方案1】:

CodesInChaos 发现了 cmets 中的问题。它源于具有返回 void 的异步方法,您几乎应该永远不要这样做 - 这意味着您无法跟踪它们。

相反,如果您的异步方法没有任何实际值要返回,您应该让它们返回 Task

发生的情况是 ParseSpecific 仅同步运行,直到第一个 await QueryWebPage(v) 未立即完成。然后它又回来了……所以任务从这里开始:

await Task.Run(() => _handler.ParseSpecific(mUrls));

... 立即完成,并打印“完成”。

一旦你的所有异步方法都返回Task,你就可以等待它们了。你也根本不需要Task.Run。所以你有:

public async void DoWork()
{
    RequestHandler _handler = new RequestHandler();
    string[] mUrls;
    await _handler.ParseSpecific(mUrls);
    Console.WriteLine("Done...");
}

...

public async TaskParseSpecific(string[] urls)
{
    foreach(string v in urls)
    {
        // Refactored for readability, although I'm not sure it really
        // makes sense now that it's clearer! Are you sure this is what
        // you want?
        var page = await QueryWebPage(v);
        Queue.Add(new Query(page, false);
    }

    Queue.CompleteAdding();
    await ConsumerTask;
    await ParseAll(true);
}

您的Reinit 方法也需要更改,因为目前ConsumerTask 基本上会立即完成,因为Consume 将立即返回,因为它是另一个返回void 的异步方法。

说实话,如果没有正确理解 async/await,您所看到的内容看起来非常复杂。我会阅读更多关于 async/await 的内容,然后可能从头开始。我强烈怀疑您可以使这变得非常简单。您可能还想阅读TPL Dataflow,它旨在简化生产者/消费者场景。

【讨论】:

  • @Nefarion 只是返回 Task 并没有帮助。你仍然需要等待它。如果您希望它在单独的线程上运行(您可能不需要),您只需要TaskEx.Run
  • 感谢两位的大力帮助,我不知道“async void”方法不可等待 :) 我修复了所有问题,现在我在这里遇到了一个有趣的小错误:Parallel.ForEach(mCollection, v =&gt; Queue.Add(new Query(url, BoolField:false)));关于 BlockingCollection 没有打开写入,所以我假设它继续而不等待并行循环完成......有什么建议吗?
  • @Nefarion:正如我所说,您的代码中还有很多需要修复的地方——但不仅仅是尝试修复它,我会退后一步,了解更多关于 async/await 的信息,并查看数据流。然后以全新的视角重新审视您的问题。
  • 感谢大家的帮助! @JonSkeet:我的程序由几个 dll 和许多其他类组成,我尽可能简化和减少了示例,并在可能的情况下省略了方法,所以我可以给出一个概述。真正的程序看起来有很大的不同,但是这个例子很好地解释了我的情况,所以我选择了它。我刚刚用一个简单的foreach 循环替换了Parallel.ForEach,效果很好,今晚我会看看这个问题。感谢async void 的精彩提示:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多