C# 如何将 Async 与 ForEach 结合使用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18667633/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I use Async with ForEach?
提问by James Jeffery
Is it possible to use Async when using ForEach? Below is the code I am trying:
使用 ForEach 时是否可以使用 Async?下面是我正在尝试的代码:
using (DataContext db = new DataLayer.DataContext())
{
db.Groups.ToList().ForEach(i => async {
await GetAdminsFromGroup(i.Gid);
});
}
I am getting the error:
我收到错误:
The name 'Async' does not exist in the current context
当前上下文中不存在名称“Async”
The method the using statement is enclosed in is set to async.
using 语句所包含的方法设置为 async。
回答by James Jeffery
The problem was that the async
keyword needs to appear before the lambda, not before the body:
问题是async
关键字需要出现在 lambda 之前,而不是正文之前:
db.Groups.ToList().ForEach(async (i) => {
await GetAdminsFromGroup(i.Gid);
});
回答by Stephen Cleary
List<T>.ForEach
doesn't play particularly well with async
(neither does LINQ-to-objects, for the same reasons).
List<T>.ForEach
不太好用async
(LINQ-to-objects 也不是,出于同样的原因)。
In this case, I recommend projectingeach element into an asynchronous operation, and you can then (asynchronously) wait for them all to complete.
在这种情况下,我建议将每个元素投影到异步操作中,然后您可以(异步)等待它们全部完成。
using (DataContext db = new DataLayer.DataContext())
{
var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
var results = await Task.WhenAll(tasks);
}
The benefits of this approach over giving an async
delegate to ForEach
are:
这种方法比给予async
委托的好处ForEach
是:
- Error handling is more proper. Exceptions from
async void
cannot be caught withcatch
; this approach will propagate exceptions at theawait Task.WhenAll
line, allowing natural exception handling. - You know that the tasks are complete at the end of this method, since it does an
await Task.WhenAll
. If you useasync void
, you cannot easily tell when the operations have completed. - This approach has a natural syntax for retrieving the results.
GetAdminsFromGroupAsync
sounds like it's an operation that produces a result (the admins), and such code is more natural if such operations can returntheir results rather than setting a value as a side effect.
- 错误处理更妥当。异常
async void
不能被捕获catch
;这种方法将在await Task.WhenAll
生产线上传播异常,允许自然的异常处理。 - 您知道任务在此方法结束时完成,因为它执行
await Task.WhenAll
. 如果使用async void
,则无法轻易判断操作何时完成。 - 这种方法具有用于检索结果的自然语法。
GetAdminsFromGroupAsync
听起来这是一个产生结果的操作(管理员),如果此类操作可以返回其结果而不是将值设置为副作用,则此类代码更自然。
回答by JD Courtoy
This little extension method should give you exception-safe async iteration:
这个小扩展方法应该为您提供异常安全的异步迭代:
public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
foreach (var value in list)
{
await func(value);
}
}
Since we're changing the return type of the lambda from void
to Task
, exceptions will propagate up correctly. This will allow you to write something like this in practice:
由于我们将 lambda 的返回类型从void
更改为Task
,因此异常将正确传播。这将允许您在实践中编写如下内容:
await db.Groups.ToList().ForEachAsync(async i => {
await GetAdminsFromGroup(i.Gid);
});
回答by superlogical
Add this extension method
添加此扩展方法
public static class ForEachAsyncExtension
{
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current).ConfigureAwait(false);
}));
}
}
And then use like so:
然后像这样使用:
Task.Run(async () =>
{
var s3 = new AmazonS3Client(Config.Instance.Aws.Credentials, Config.Instance.Aws.RegionEndpoint);
var buckets = await s3.ListBucketsAsync();
foreach (var s3Bucket in buckets.Buckets)
{
if (s3Bucket.BucketName.StartsWith("mybucket-"))
{
log.Information("Bucket => {BucketName}", s3Bucket.BucketName);
ListObjectsResponse objects;
try
{
objects = await s3.ListObjectsAsync(s3Bucket.BucketName);
}
catch
{
log.Error("Error getting objects. Bucket => {BucketName}", s3Bucket.BucketName);
continue;
}
// ForEachAsync (4 is how many tasks you want to run in parallel)
await objects.S3Objects.ForEachAsync(4, async s3Object =>
{
try
{
log.Information("Bucket => {BucketName} => {Key}", s3Bucket.BucketName, s3Object.Key);
await s3.DeleteObjectAsync(s3Bucket.BucketName, s3Object.Key);
}
catch
{
log.Error("Error deleting bucket {BucketName} object {Key}", s3Bucket.BucketName, s3Object.Key);
}
});
try
{
await s3.DeleteBucketAsync(s3Bucket.BucketName);
}
catch
{
log.Error("Error deleting bucket {BucketName}", s3Bucket.BucketName);
}
}
}
}).Wait();
回答by mrogunlana
Here is an actual working version of the above async foreach variants with sequential processing:
这是上述带有顺序处理的异步 foreach 变体的实际工作版本:
public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
foreach (var item in enumerable)
await Task.Run(() => { action(item); }).ConfigureAwait(false);
}
Here is the implementation:
这是实现:
public async void SequentialAsync()
{
var list = new List<Action>();
Action action1 = () => {
//do stuff 1
};
Action action2 = () => {
//do stuff 2
};
list.Add(action1);
list.Add(action2);
await list.ForEachAsync();
}
What's the key difference? .ConfigureAwait(false);
which keeps the context of main thread while async sequential processing of each task.
关键区别是什么?.ConfigureAwait(false);
它在每个任务的异步顺序处理时保持主线程的上下文。
回答by RubberDuck
The simple answer is to use the foreach
keyword instead of the ForEach()
method of List()
.
简单的答案是使用foreach
关键字而不是 的ForEach()
方法List()
。
using (DataContext db = new DataLayer.DataContext())
{
foreach(var i in db.Groups)
{
await GetAdminsFromGroup(i.Gid);
}
}
回答by Andrei Krasutski
Starting with C# 8.0
, you can create and consume streams asynchronously.
从 开始C# 8.0
,您可以异步创建和使用流。
private async void button1_Click(object sender, EventArgs e)
{
IAsyncEnumerable<int> enumerable = GenerateSequence();
await foreach (var i in enumerable)
{
Debug.WriteLine(i);
}
}
public static async IAsyncEnumerable<int> GenerateSequence()
{
for (int i = 0; i < 20; i++)
{
await Task.Delay(100);
yield return i;
}
}