异步运行同一方法的多个实例?
我的要求很奇怪。
我有SomeMethod()
调用GetDataFor()
。
public void SomeMethod() { for(int i = 0; i<100; i++) { var data = GetDataFor(i); } } public data GetDataFor(int i) { //call a remote API //to generate data for i //store to database return data; }
对于每个i
,最终结果将始终不同。 在调用GetDataFor(i+1)
之前,无需等待 GetDataFor(i)
完成。
换句话说,我需要:
- 在成功调用
i
后立即为每个i+1
调用GetDataFor()
(并行调用它们看起来不可能) - 等到
GetDataFor()
所有 100个实例都完成运行 - 离开
SomeMethod()
的范围
根据YK1的回答 ,我试图像这样修改它:
public async Task SomeMethod() { for(int i = 0; i GetDataFor(i)); var data = await task; } }
它没有抛出任何错误,但我需要理解这背后的概念:
-
task
如何区分不同的await
请求? 它越来越多了。 - 这样做是否明显错误? 那么,怎么做呢?
你可以使用Parallel.For
:
public void SomeMethod() { Parallel.For(0, 100, i => { var data = GetDataFor(i); //Do something }); } public data GetDataFor(int i) { //generate data for i return data; }
编辑:
并行循环的语法与您已知的for
和foreach
循环非常相似,但并行循环在具有可用内核的计算机上运行得更快。 另一个区别是,与顺序循环不同,没有为并行循环定义执行顺序。 步骤通常同时并行进行。 有时,两个步骤的顺序与循环顺序时相反。 唯一的保证是所有循环的迭代都将在循环结束时运行。
对于并行循环,并行程度不需要由代码指定。 相反,运行时环境在尽可能多的内核上同时执行循环的步骤。 无论有多少核心,循环都能正常工作。 如果只有一个核心,则性能接近(相当于几个百分点)顺序等效。 如果有多个内核,性能会提高; 在许多情况下,性能与核心数量成比例地提高。
您可以在此处查看更详细的说明。
我会将每个任务添加到集合中,然后在循环之后等待整个集合。
等待在这样的循环内部会产生大量的延续和更多的开销,包括在继续循环之前等待每个调用完成我相信。
看一下等待Task.WaitAll 。
如果相反,每个任务的值对于处理很重要,那么请查看等待Task.WhenAll然后将每个Task的结果读入您的返回集合中。
有几种不同的方法。
首先,您可以保持同步并且只是并行执行它们(在不同的线程上)。 如果要在继续之前收集调用方法中的所有结果,并行LINQ优于Parallel
:
public data[] SomeMethod() { return Enumerable.Range(0, 100) .AsParallel().AsOrdered() .Select(GetDataFor).ToArray(); }
其次,你可以使它异步。 要使某些东西真正异步,您需要从最低级别开始(在这种情况下,“调用远程API”和“存储到数据库”)并首先进行异步。 然后你可以使GetDataFor
异步:
public async Task GetDataForAsync(int i) { await .. //call a remote API asynchronously await .. //store to database asynchronously return data; }
然后你也可以使SomeMethod
异步:
public Task SomeMethodAsync() { return Task.WhenAll( Enumerable.Range(0, 100).Select(GetDataForAsync) ); }
使代码异步是更多的工作 – 更多的代码必须改变 – 但它在可伸缩性和资源使用方面更好。
当使用async
await
你实际上是在说“在等待这项任务完成时,请继续做一些不依赖于此任务的独立工作”。 因为你不关心等待GetDataFor完成你真的不想使用async
await
。
此前的问题似乎与您的问题非常相似。 考虑到这一点,我认为你应该能够做到这样的事情:
public void SomeMethod() { Task.Run(() => GetDataFor(i)); }
基本上,这假设你不需要等待GetDataFor完成任何其他事情,它实际上是“火上浇油而忘记”。
关于Parallel.For,只要你有超过1个核心,你可能会看到性能有所改善。 如果没有,您可能会看到性能略有下降(更多开销)。 这篇文章有助于解释它是如何工作的。
UPDATE
根据你的评论,我会建议像:
var TList = new List(); for (var i = 0; i < 100; i++) { TList.Add(Task.Run(() => GetDataFor(i))); } await Task.WhenAll(TList);
这是一个有用的问题 ,突出了为什么你可能想要使用WhenAll而不是WaitAll。
您可能希望在完成任务的状态中包含一些检查,以查看哪些失败(如果有)。 请看这里的例子。
代码实际上没有任何意义。
任务如何区分不同的等待呼叫? 它越来越多了。
它不会被覆盖。 因为…
for(int i = 0; i < 100; i++) { var task = Task.Run(() => GetDataFor(i)); var data = await task; }
这是在继续循环之前为每个请求完成等待。 等待结束。
这意味着整个任务是无关紧要的 – 这里没有任何并行发生。 你可以在没有任务的情况下减少一些小额开销。
我怀疑OP想要实现他根本没有做到的事情,并且他没有花足够的时间进行调试以意识到他已经再次单线程化整个循环。
虽然您的原始代码会覆盖这些值,但您似乎正在尝试将并行操作的结果组合在一起。 如果是这样,请考虑使用Task.ContinueWith来处理返回值。 您的代码看起来像这样:
public void SomeMethod() List tasks = new List (); for (var i = 0; i < 100; i++) { tasks.Add(Task.Run(() => GetDataFor(i)).ContinueWith((antecedent) => { // Process the results here. })); } Task.WaitAll(tasks.ToArray()); }