C# 等待不阻塞,直到任务完成

C# 等待不阻塞,直到任务完成,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我正试图用等待ConsumerTask阻止RequestHandler.ParseAll(),但当我在那里设置断点时,我总是先得到“完成…”输出。。。然后,Parse2()失败,出现NullReferenceException。(这是我的猜测:“GC开始清理,因为\u handler超出了范围”) 不管怎样,我不明白为什么会这样 class MainClass { public async void DoWork() { RequestHandler _handl

我正试图用
等待ConsumerTask阻止
RequestHandler.ParseAll()
,但当我在那里设置断点时,我总是先得到“完成…”输出。。。然后,
Parse2()
失败,出现NullReferenceException。(这是我的猜测:“GC开始清理,因为
\u 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;
    }
}
class类main类
{
公共异步void DoWork()
{
RequestHandler _handler=新的RequestHandler();
字符串[]mUrls;
/*在这里用值填充mUrls*/
wait Task.Run(()=>_handler.ParseSpecific(mUrls));
控制台。WriteLine(“完成…”);
}
}
静态类解析器
{
公共静态异步任务QueryWebPage(字符串url){/*查询url*/}
公共静态异步任务Parse1(查询)
{ 
Parallel.ForEach(/*此处为流程数据*/);
}
公共静态异步任务解析2(查询)
{
foreach(query.WebPage中的字符串行)
/*这里我得到一个NullReference异常,因为query.WebPage==null*/
}
}
密封类RequestHandler
{
私有阻塞收集队列;
私有任务ConsumerTask=Task.Run(()=>/*为队列中的每个元素调用consume()*/);
专用异步无效使用(查询对象)
{
wait(obj.BoolField?Parser.Parse1(obj):Parser.Parse2(obj));
}
公共异步void ParseSpecific(字符串[]URL)
{
foreach(URL中的字符串v)
Add(新查询(wait QueryWebPage(v),BoolField:false));
Queue.CompleteAdding();
等待消费者任务;
等待ParseAll(true);
}
专用异步任务ParseAll(仅限bool部分)
{
雷尼特();
Parallel.ForEach(mCollection,v=>Queue.Add(新查询(url,BoolField:false));
Queue.CompleteAdding();
等待消费者任务;
/*进一步加工材料*/
}
}
结构查询
{
公共只读字符串[]网页;
公共只读布尔布尔菲尔德;
公共查询(uint e,IEnumerable页,bool b):this()
{
Webpage=page.ToArray();
布尔菲尔德=b;
}
}

CodesInChaos在评论中发现了这个问题。它源于异步方法返回
void
,这是你几乎不应该做的——这意味着你没有办法跟踪它们

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

发生的情况是,
ParseSpecific
只同步运行,直到第一个
wait QueryWebPage(v)
没有立即完成。然后它又回来了。。。因此,任务从这里开始:

await Task.Run(() => _handler.ParseSpecific(mUrls));
。。。立即完成,并打印“完成”

一旦让所有异步方法返回
Task
,您就可以等待它们了。您也不需要执行
任务。请运行
。所以你会:

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

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


老实说,如果没有对async/await的正确理解,您得到的看起来非常复杂。我会阅读更多关于async/await的内容,然后可能从头开始。我强烈怀疑你能把这件事做得简单得多。您可能还想了解哪些是为了简化生产者/消费者场景而设计的。

您的示例非常复杂(对于许多没有
async
后缀的异步方法来说更是如此)-您是否能够将其缩减?
ParseSpecific
在其第一个
wait
语句中返回。你不必等待ParseSpecific
@JonSkeet:我会尽可能地把它删减,而不会留下任何重要的东西。。。但我会努力的。@Nefarion:CodesInChaos在他的评论中找到了答案,我正在详细说明。@CodesInChaos:我希望你不介意我基本上是因为你发现了错误:)很好,先生@Nefarion只是返回
任务
没有帮助。你仍然需要等待它。如果你想在一个单独的线程上运行它,你只需要
TaskEx.Run
(你可能不需要)。感谢你们两位的巨大帮助,我不知道“async void”方法是不可能实现的:)我修复了这一切,现在我在这里遇到了一个有趣的小错误:
Parallel.ForEach(mCollection,v=>Queue.Add(新查询(url,BoolField:false)));关于BlockingCollection没有开放供编写,因此我假设它在不等待并行循环完成的情况下继续。。。有什么建议吗?@Nefarion:正如我所说的,你的代码中还有很多问题需要解决——但与其只是尝试解决,不如退一步,了解更多关于async/await的信息,并了解数据流。然后用全新的视角重新审视你的问题。谢谢大家的帮助@JonSkeet:我的程序是由几个dll和许多其他类组成的,我尽可能地简化和减少了示例,并尽可能地省略了方法,因此我可以给出一个概述。真正的程序看起来很不一样,但是这个例子很好地解释了我的情况,所以我选择了它。我刚刚用一个简单的
ForEach
循环替换了
Parallel.ForEach
,效果很好,今晚我将研究这个问题。感谢您提供有关
异步void
的精彩提示:)
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);
}