【问题标题】:Awaiting IEnumerable<Task<T>> individually C#单独等待 IEnumerable<Task<T>> C#
【发布时间】:2015-12-22 02:00:59
【问题描述】:

简而言之,我有一个可枚举的任务,我想以 await 方式运行数组中的每个任务。每个任务都将执行缓慢的网络操作,因此我只需要在任务完成后更新 WinForm UI。

以下是我目前正在使用的代码,但我认为这更像是一种 hack,而不是实际的解决方案:

private void btnCheckCredentials_Click(object sender, EventArgs e)
{
    // GetNetCredentials() is irrelevant to the question...
    List<NetworkCredential> netCredentials = GetNetCredentials();

    // This call is not awaited. Displays warning!
    netCredentials.ForEach(nc => AwaitTask(ValidateCredentials(nc)));
}

public async Task<bool> ValidateCredentials(NetworkCredential netCredential)
{
    // Network-reliant, slow code here...
}

public async Task AwaitTask(Task<bool> task)
{
    await task;

    // Dumbed-down version of displaying the task results
    txtResults.Text += task.Result.ToString();
}

显示第二行 btnCheckCredentials_Click() 警告:

由于不等待此调用,因此在调用完成之前继续执行当前方法。考虑将 'await' 运算符应用于调用结果。

这实际上按我想要的方式工作,因为我不想等待操作完成。相反,我只想启动任务,然后在每个任务完成后立即做一些事情。

Task.WhenAny() 或 Task.WhenAll() 方法确实如我所愿,因为我想知道每项任务的完成情况——只要它完成。 Task.WaitAll() 或 Task.WaitAny() 是阻塞的,因此也是不可取的。

编辑:所有任务应该同时开始。然后他们可以按任何顺序完成。

【问题讨论】:

    标签: winforms c#-4.0 async-await task


    【解决方案1】:

    您可以通过使用Task.WhenAny 将其标记为async 来做到这一点(async void 很好,因为您在事件处理程序中):

    private async void btnCheckCredentials_Click(object sender, EventArgs e)
    {
        // GetNetCredentials() is irrelevant to the question...
        List<NetworkCredential> netCredentials = GetNetCredentials();
        var credentialTasks = netCredentials
                              .Select(cred => ValidateCredentialsAsync(cred))
                              .ToList();
    
        while (credentialTasks.Count > 0)
        {
            var finishedTask = await Task.WhenAny(credentialTasks);
    
            // Do stuff with finished task.
    
            credentialTasks.Remove(finishedTask);
        }
    }
    

    【讨论】:

    • 这不是在前一个任务完成后立即开始每个任务吗?抱歉,如果我的问题没有很清楚地反映这一点,但我希望所有任务一起开始。
    • @ZuiqPazu 哦。我知道了。我将编辑我的答案以使用Task.WhenAny,这就是您所需要的。
    • 我也不认为这是正确的。正如我在最初的问题中提到的那样,我需要在每项任务完成后立即做一些事情。据我了解,Task.WhenAny() 仅在第一个完成任务时触发。
    • @ZuiqPazu 它会在你完成后立即给你任务,如果你遍历整个任务列表,你可以得到它们。看我的回答。
    • 这具有二次性能成本,我认为它是相当肮脏的命令式代码......
    【解决方案2】:

    您可以触发并忘记每个任务,并在任务完成时添加回调。

    private async void btnCheckCredentials_Click(object sender, EventArgs e)
    {
    List<NetworkCredential> netCredentials = GetNetCredentials();
    
        foreach (var credential in netCredentials)
        {
            ValidateCredentails(credential).ContinueWith(x=> ...) {
    
            };
         }
    }
    

    因此,您可以创建回调方法而不是 labda 表达式,并准确知道特定任务何时完成。

    【讨论】:

    • 我认为这可以解决部分问题。这样我就不必调用另一个方法来处理向 UI 显示结果。但这仍然会显示我目前在实施时面临的警告。
    【解决方案3】:

    你在找Task.WhenAll吗?

    await Task.WhenAll(netCredentials.Select(nc => AwaitTask(ValidateCredentials(nc)));
    

    您可以在AwaitTask 中完成所有您需要的完成处理。

    await task; 有点尴尬。我会这样做:

    public async Task AwaitTask(netCredential credential)
    {
        var result = await ValidateCredentails(credential);
    
        // Dumbed-down version of displaying the task results
        txtResults.Text += result.ToString();
    }
    

    【讨论】:

    • 不是只有在所有任务完成后才会触发吗?
    • @ZuiqPazu AwaitTask 在每个任务完成后立即运行。我想这就是你想要的(?)。
    • 鉴于新的编辑,它现在看起来非常接近我的原始代码。
    • @ZuiqPazu 这能回答问题吗?还有其他问题吗?
    • 这确实回答了这个问题。有点不相关,但只是在我接受你的回答之前确认一下:你会说 async / await 是依赖 IO 和 CPU-lite 功能的方法 - 还是你会建议一种不同的线程方法?
    猜你喜欢
    • 2013-04-26
    • 1970-01-01
    • 2020-08-22
    • 1970-01-01
    • 2018-10-13
    • 1970-01-01
    • 2013-06-07
    相关资源
    最近更新 更多