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