[英]How to use await in a loop
I'm trying to create an asynchronous console app that does a some work on a collection.我正在尝试创建一个异步控制台应用程序来对集合执行一些操作。 I have one version which uses parallel for loop another version that uses async/await.
我有一个版本使用并行循环,另一个版本使用异步/等待。 I expected the async/await version to work similar to parallel version but it executes synchronously.
我希望 async/await 版本的工作方式类似于并行版本,但它是同步执行的。 What am I doing wrong?
我究竟做错了什么?
public class Program
{
public static void Main(string[] args)
{
var worker = new Worker();
worker.ParallelInit();
var t = worker.Init();
t.Wait();
Console.ReadKey();
}
}
public class Worker
{
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
foreach(var i in series)
{
Console.WriteLine("Starting Process {0}", i);
var result = await DoWorkAsync(i);
if (result)
{
Console.WriteLine("Ending Process {0}", i);
}
}
return true;
}
public async Task<bool> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return true;
}
public bool ParallelInit()
{
var series = Enumerable.Range(1, 5).ToList();
Parallel.ForEach(series, i =>
{
Console.WriteLine("Starting Process {0}", i);
DoWorkAsync(i);
Console.WriteLine("Ending Process {0}", i);
});
return true;
}
}
The way you're using the await
keyword tells C# that you want to wait each time you pass through the loop, which isn't parallel.您使用
await
关键字的方式告诉 C# 您希望每次通过循环时都等待,这不是并行的。 You can rewrite your method like this to do what you want, by storing a list of Task
s and then await
ing them all with Task.WhenAll
.你可以像这样重写你的方法来做你想做的事,通过存储一个
Task
列表,然后用Task.WhenAll
await
它们。
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
var tasks = new List<Task<Tuple<int, bool>>>();
foreach (var i in series)
{
Console.WriteLine("Starting Process {0}", i);
tasks.Add(DoWorkAsync(i));
}
foreach (var task in await Task.WhenAll(tasks))
{
if (task.Item2)
{
Console.WriteLine("Ending Process {0}", task.Item1);
}
}
return true;
}
public async Task<Tuple<int, bool>> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return Tuple.Create(i, true);
}
Your code waits for each operation (using await
) to finish before starting the next iteration.您的代码在开始下一次迭代之前等待每个操作(使用
await
)完成。
Therefore, you don't get any parallelism.因此,您不会获得任何并行性。
If you want to run an existing asynchronous operation in parallel, you don't need await
;如果要并行运行现有的异步操作,则不需要
await
; you just need to get a collection of Task
s and call Task.WhenAll()
to return a task that waits for all of them:您只需要获取
Task
的集合并调用Task.WhenAll()
以返回等待所有这些的任务:
return Task.WhenAll(list.Select(DoWorkAsync));
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5);
Task.WhenAll(series.Select(i => DoWorkAsync(i)));
return true;
}
In C# 7.0 you can use semantic names to each of the members of the tuple , here is Tim S. 's answer using the new syntax:在 C# 7.0 中, 您可以对 tuple 的每个成员使用语义名称,这是Tim S.使用新语法的答案:
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
var tasks = new List<Task<(int Index, bool IsDone)>>();
foreach (var i in series)
{
Console.WriteLine("Starting Process {0}", i);
tasks.Add(DoWorkAsync(i));
}
foreach (var task in await Task.WhenAll(tasks))
{
if (task.IsDone)
{
Console.WriteLine("Ending Process {0}", task.Index);
}
}
return true;
}
public async Task<(int Index, bool IsDone)> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return (i, true);
}
You could also get rid of task.
你也可以摆脱
task.
inside foreach
:里面
foreach
:
// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
if (IsDone)
{
Console.WriteLine("Ending Process {0}", Index);
}
}
// ...
We can use async method in foreach loop to run async API calls.我们可以在 foreach 循环中使用异步方法来运行异步 API 调用。
public static void Main(string[] args)
{
List<ZoneDetails> lst = GetRecords();
foreach (var item in lst)
{
//For loop run asyn
var result = GetAPIData(item.ZoneId, item.fitnessclassid).Result;
if (result != null && result.EventHistoryId != null)
{
UpdateDB(result);
}
}
}
private static async Task<FODBrandChannelLicense> GetAPIData(int zoneId, int fitnessclassid)
{
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = HttpClient.GetAsync(new Uri(url)).Result;
var content = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<Model>(content);
if (response.EnsureSuccessStatusCode().IsSuccessStatusCode)
{
Console.WriteLine($"API Call completed successfully");
}
return result;
}
To add to the already good answers here, it's always helpful to me to remember that the async method returns a Task .为了补充这里已经很好的答案,记住 async 方法返回一个Task总是对我有帮助。
So in the example in this question, each iteration of the loop has await
.所以在这个问题的例子中,循环的每次迭代都有
await
。 This causes the Init()
method to return control to its caller with a Task<bool>
- not a bool.这会导致
Init()
方法使用Task<bool>
而非 bool将控制权返回给其调用者。
Thinking of await as just a magic word that causes execution state to be saved, then skipped to the next available line until ready, encourages confusion: "why doesn't the for loop just skip the line with await and go to the next statement?"认为 await 只是一个导致执行状态被保存的魔法词,然后跳到下一个可用的行直到准备好,这会鼓励混淆:“为什么 for 循环不只是跳过带有 await 的行并转到下一条语句? ”
If instead you think of await as something more like a yield statement, that brings a Task
with it when it returns control to the caller, in my opinion flow starts to make more sense: "the for loop stops at await, and returns control and the Task
to the caller. The for loop won't continue until that is done."相反,如果你认为 await 更像是一个 yield 语句,当它把控制返回给调用者时,它会带来一个
Task
,在我看来,流程开始变得更有意义:“for 循环在 await 处停止,并返回控制和调用者的Task
。在完成之前,for 循环不会继续。”
Sometimes, all we need is a copy + paste ready solution, thus, this is the whole solution of Tim.S. 有时,我们所需要的只是一个可复制 + 粘贴的解决方案,因此,这就是Tim.S的整个解决方案。 :
:
namespace AsyncSimple
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
class Startup
{
static void Main()
{
var worker = new Worker();
worker.ParallelInit();
var t = worker.Init();
t.Wait();
Console.ReadKey();
}
}
public class Worker
{
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
var tasks = new List<Task<Tuple<int, bool>>>();
foreach (var i in series)
{
Console.WriteLine("Starting Process {0}", i);
tasks.Add(DoWorkAsync(i));
}
foreach (var task in await Task.WhenAll(tasks))
{
if (task.Item2)
{
Console.WriteLine("Ending Process {0}", task.Item1);
}
}
return true;
}
public async Task<Tuple<int, bool>> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return Tuple.Create(i, true);
}
public bool ParallelInit()
{
var series = Enumerable.Range(1, 5).ToList();
Parallel.ForEach(series, i =>
{
Console.WriteLine("Starting Process {0}", i);
DoWorkAsync(i);
Console.WriteLine("Ending Process {0}", i);
});
return true;
}
}
}
And the result in the console: 结果在控制台中:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.