通过等待每个任务,异步转换IEnumerable<Task<T>>

27

今天我在思考如何通过等待每个任务来转换任务列表。 请考虑以下示例:

private static void Main(string[] args)
{
    try
    {
        Run(args);                
        Console.ReadLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
        Console.ReadLine();
    }
}

static async Task Run(string[] args)
{
    //Version 1: does compile, but ugly and List<T> overhead
    var tasks1 = GetTasks();                       

    List<string> gainStrings1 = new List<string>();
    foreach (Task<string> task in tasks1)
    {
        gainStrings1.Add(await task);
    }
    Console.WriteLine(string.Join("", gainStrings1));

    //Version 2: does not compile
    var tasks2 = GetTasks();
    IEnumerable<string> gainStrings2 = tasks2.Select(async t => await t);
    Console.WriteLine(string.Join("", gainStrings2));
}

static IEnumerable<Task<string>> GetTasks()
{
    string[] messages = new[] { "Hello", " ", "async", " ", "World" };

    for (int i = 0; i < messages.Length; i++)
    {
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
        tcs.SetResult(messages[i]);
        yield return tcs.Task;
    }
}

我想在不使用foreach的情况下转换我的任务列表,但是无论是匿名函数语法还是常规函数语法都不能像foreach那样工作。

我必须依赖于我的foreach和List<T>,还是有一种方法可以让它与IEnumerable<T>及其所有优势一起工作?


第二个为什么不能编译?错误信息是什么?如果在“Select”后添加缺失的“ToLis()”,它会编译吗? - Daniel Hilgarth
1
这是因为它返回 IEnumerable<Task<string>> - GameScripting
1个回答

56
这个怎么样:
await Task.WhenAll(tasks1);
var gainStrings = tasks1.Select(t => t.Result).ToList();

等待所有任务结束后提取结果。如果您不关心它们完成的顺序,这是理想的方法。

编辑2: 更好的方法:

var gainStrings = await Task.WhenAll(tasks1);

3
可以直接使用 WhenAll 的返回值,而不必使用 SelectWhenAll 的返回值是一个 string[],包含了每个任务的结果。 - Servy
根据这个链接:http://msdn.microsoft.com/en-us/library/hh194874.aspx,只有在给它一个数组时它才会执行该操作。 - Toni Petrina
3
不需要使用 ToArray()Task.WhenAll() 也适用于 IEnumerable<Task<T>> - svick
5
Nope方法会返回一个结果数组,即使你传递了一个 IEnumerable<Task<TResult>> - Servy
EDIT2更好,因为有时Visual Studio会警告在任务上调用.Result。 - eyelesscactus54

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接