在Task完成之前等待不阻塞

Nef*_*ion 2 c# asynchronous async-await

我试图阻止RequestHandler.ParseAll()await ConsumerTask;,但是当我设置一个断点,我总是第一个获得"完成......"输出...然后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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 6

CodesInChaos在评论中发现了问题.它源于异步方法的返回void,你几乎会这样做 - 这意味着你无法跟踪它们.

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

发生的事情ParseSpecific只是同步运行,直到第一个await QueryWebPage(v)没有立即完成.它然后返回...所以任务从这里开始:

await Task.Run(() => _handler.ParseSpecific(mUrls));
Run Code Online (Sandbox Code Playgroud)

...立即完成,并打印"完成".

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

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

...

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);
}
Run Code Online (Sandbox Code Playgroud)

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

说实话,你所拥有的东西看起来非常复杂,没有正确理解async/await.我会在async/await上阅读更多信息,然后可能从头开始.我强烈怀疑你能做到这么多,简单得多.您可能还想阅读TPL Dataflow,它旨在简化生产者/消费者场景.