我有一个有点奇怪的案例。我有一个看起来像这样的列表:
var myStuff = [A:1, A:2, A:3, B:1, B:2, C:1, C:2, C:3, C:4];
对于所有信件,我想为除最后一个版本之外的每个版本启动任务。我想在以前的版本完成后运行最后一个版本。为了更好地解释:
对 B 和 C 重复此操作,最好同时运行 A:1、B:1、C:1、A:2、B:2 等。
我想我可以使用 Linq Groupby 将列表分成 3 组,按版本排序,然后启动除最后一个之外的所有任务。我不知道如何以一种既可以让最后一个版本(A:3、B:2、C:4)等待之前的任务,同时又确保 A:1、B:1、 C:1、A:2、B:2、C:2 等都同时运行。
这可能吗?
作为第一种方法,解决方案可能如下所示:
(char, int)[] myStuff = [('A', 1), ('A', 2), ('A', 3), ('B', 1), ('B', 2), ('C', 1), ('C', 2), ('C', 3), ('C', 4)];
var groups = myStuff.GroupBy(x => x.Item1).Select(HandleGroup);
await Task.WhenAll(groups);
static async Task HandleGroup(IGrouping<char, (char, int)> gr)
{
var ordered = gr.OrderBy(x => x.Item2).ToArray();
var prior = ordered.Take(ordered.Length - 1).Select(HandleTuple);
await Task.WhenAll(prior);
await HandleTuple(ordered.Last());
async Task HandleTuple((char, int) tuple)
{
await Task.Delay(tuple.Item2 * 1000);
Console.WriteLine($"Item: {tuple} has been handled");
}
}
将您的项目更改为元组,因为代码不正确。
希望它能向您展示这个想法。
我将首先讨论一些基础知识。通常,在使用
async
时,您希望 await
您的任务:
await SomeMethodAsync(myData);
这里的第一个问题不仅仅是一个项目,而是一个完整的数组。即使假设您知道自己有多少物品,您也不会想做这样的事情:
await SomeMethodAsync(myData[0]);
await SomeMethodAsync(myData[1]);
await SomeMethodAsync(myData[2]);
这实际上让你重新按顺序运行事情。记住
async
方法总是返回一个任务,你可以这样做:
await Task.WhenAll(myData.Select(async item => SomeMethodAsync(item));
但我们还没有完全做到这一点。您需要排除 IEnumerable 中的最后一项,以便在其他项完成之前它不会运行。值得庆幸的是,C# 有一些很好的 范围和索引支持 使这变得更容易;
await Task.WhenAll(myData[..^1].Select(async item => SomeMethodAsync(item));
await myData[^1];
现在已经非常接近了,但这只是数据中的一组。让我们再次扩展它以支持多个组。
不幸的是,我不知道
A:1
、A:2
、B:1
等项目的结构。对于这段代码,我假设它们是字符串。这给了我足够的时间,我也可以开始使用示例问题中的 myStuff
变量:
var groups = myStuff.GroupBy(s => s.Split(':')[0])
await Task.WhenAll(async g =>
groups.Select(async d => {
// need an array or list instead of IGrouping enumerable for index support
var data = d.ToList();
await Task.WhenAll(data[..^1].Select(asycn item => SomeMethodAsync(item));
await data[^1];
});
);
需要获取上述代码中每个组的列表并不是很好,但是高效地从一组未知大小中排除最后一项是很棘手的(
Last()
对于这种情况来说不太好)。您可以通过缓冲来做到这一点,但在这种情况下,该代码的维护和复杂性成本可能超过了胜利。