使用ForEach时是否可以使用Async?下面是我正在尝试的代码:
using (DataContext db = new DataLayer.DataContext())
{
db.Groups.ToList().ForEach(i => async {
await GetAdminsFromGroup(i.Gid);
});
}
我收到错误消息:
名称“异步”在当前上下文中不存在
包含using语句的方法设置为async。
使用ForEach时是否可以使用Async?下面是我正在尝试的代码:
using (DataContext db = new DataLayer.DataContext())
{
db.Groups.ToList().ForEach(i => async {
await GetAdminsFromGroup(i.Gid);
});
}
我收到错误消息:
名称“异步”在当前上下文中不存在
包含using语句的方法设置为async。
Answers:
List<T>.ForEach
不能特别好地使用async
(出于相同的原因,LINQ到对象也没有)。
在这种情况下,我建议将每个元素投影到一个异步操作中,然后您可以(异步)等待它们全部完成。
using (DataContext db = new DataLayer.DataContext())
{
var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
var results = await Task.WhenAll(tasks);
}
与async
授予代表相比,此方法的好处ForEach
是:
async void
无法捕获来自的异常catch
;这种方法将在线传播异常await Task.WhenAll
,从而允许自然的异常处理。await Task.WhenAll
。如果使用async void
,则无法轻松判断操作何时完成。GetAdminsFromGroupAsync
听起来像是一个产生结果的操作(管理员),并且如果这样的操作可以返回其结果而不是将值设置为副作用,则这种代码更为自然。async
。他们非常有帮助!
foreach
与await
你的循环体。
ForEach
仅采用同步委托类型,并且没有过载采用异步委托类型。因此,简短的答案是“没有人写异步ForEach
”。更长的答案是,您必须假设一些语义。例如,是否应一次(如foreach
)或同时(如Select
)处理这些项目?如果一次使用一个,异步流不是更好的解决方案吗?如果同时出现,结果应该按原始项目顺序还是按完成顺序排列?它应该在第一次失败时失败还是要等到全部完成之后才能失败?等等
SemaphoreSlim
限制异步任务。
这个扩展方法应该可以为您提供异常安全的异步迭代:
public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
foreach (var value in list)
{
await func(value);
}
}
由于我们将Lambda的返回类型从void
更改为Task
,因此异常会正确传播。这样您就可以在实践中编写如下内容:
await db.Groups.ToList().ForEachAsync(async i => {
await GetAdminsFromGroup(i.Gid);
});
async
应该早于i =>
ForEachAsync
本质上是一个库的方法,所以等待应该与可能配置ConfigureAwait(false)
。
简单的答案是使用foreach
关键字而不是的ForEach()
方法List()
。
using (DataContext db = new DataLayer.DataContext())
{
foreach(var i in db.Groups)
{
await GetAdminsFromGroup(i.Gid);
}
}
这是上述异步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);
}
这是实现:
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();
}
主要区别是什么?.ConfigureAwait(false);
它在异步顺序处理每个任务时保留主线程的上下文。
从开始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;
}
}
MoveNext
枚举数。这在枚举器无法立即获取下一个元素并且必须等待一个元素可用之前非常重要。
添加此扩展方法
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);
}));
}
}
然后像这样使用:
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();
问题在于async
关键字需要出现在lambda之前,而不是在正文之前:
db.Groups.ToList().ForEach(async (i) => {
await GetAdminsFromGroup(i.Gid);
});
async void
。这种方法在异常处理和知道异步操作何时完成方面存在问题。
List.ForEach()
它不是LINQ的一部分。