使用Task.WhenAll时如何获得结果

时间:2019-02-09 18:56:56

标签: c# async-await task task-parallel-library

假设我有一个添加帐户的Web服务。我应该使用此服务来添加帐户列表:

"40701", "40702", "40703", "40704", "40705"

出于测试目的,我试图模拟该服务的不稳定运行,尤其是在第一次尝试添加前三个帐户,而其他两个帐户进入第二轮的情况下。在第二次尝试中,仅添加了“ 40704”帐户,“ 40705”帐户进入了第三轮,并在第三次尝试时添加了该帐户。

public class AddingAccounts
{
    int triesCount = 0;

    // decision table to add accounts
    readonly int[][] dt =
    {
        new int[] { 1, 2, 3 },
        new int[] { 4 },
        new int[] { 5 }
    };

    List<int> result = new List<int>();

    public async Task<List<string>> GetAccountsAsync()
    {
        await Task.Delay(1500);
        return new List<string> { "40701", "40702", "40703", "40704", "40705" };
    }

    public async Task<int> AddAccount(string account)
    {
        try
        {
            await Task.Delay(1000);

            // define accounts at the current attempt
            var accountsToAdd = dt[triesCount].Select(x => $"4070{x}");

            if (accountsToAdd.Contains(account))
            {
                // simulate successful operation, return id account
                return new Random().Next(100);
            }
            else
            {
                throw new InvalidOperationException($"Account {account} was not added");
            }
        }
        catch (Exception ex)
        {
            ex.Data["account"] = account; 
            throw;
        }
    }

    public async Task<List<int>> AddAccountsAsync(List<string> accounts)
    {
        var tasks = accounts.Select(ac => AddAccount(ac));
        Task<int[]> allTasks = Task.WhenAll(tasks);

        try
        {
            var res = await allTasks; 
            result.AddRange(res);  
        }
        catch
        {
            // how can I add returned values of successfully completed tasks to result variable here ?
            // I tried to use tasks variable as John advised
            foreach (var t in tasks)
            {
                // but most tasks have WaitingForActivation status and Result of 0
                if (t.Status == TaskStatus.RanToCompletion)
                {
                    result.Add(t.Result);
                }
            }

            List<string> failedToAddAccounts = new List<string>();

            AggregateException ae = allTasks.Exception;
            foreach(var ex in ae.Flatten().InnerExceptions)
            {                   
                if (ex.Data["account"] is string failedAccount)
                {
                    failedToAddAccounts.Add(failedAccount);
                }
            }

            triesCount++;
            return await AddAccountsAsync(failedToAddAccounts);
        }

        return result;
    }
}

我想获取所有五个帐户ID。

如何在try/catch块中获得成功完成任务的结果?我的意思是在第一轮中,当我等待allTasks时,allTasks的状态为Faulted,我无法获得第一个添加帐户的返回值。

1 个答案:

答案 0 :(得分:1)

为此,您应考虑使用Microsoft的Reactive Framework(Rx)。比使用任务要容易得多,功能也更强大。

首先,我将您的测试代码重写为更加简单:

public async Task<List<string>> GetAccountsAsync()
{
    await Task.Delay(1500);
    return new List<string> { "40701", "40702", "40703", "40704", "40705" };
}

private Random _rnd = new Random();

public async Task<int> AddAccount(string account)
{
    await Task.Delay(1000);

    if (_rnd.NextDouble() > 0.5)
    {
        return _rnd.Next(100);
    }
    else
    {
        Console.WriteLine("!");
        throw new InvalidOperationException($"Account {account} was not added");
    }
}

现在,使用Rx轻而易举:

var query =
    from accounts in Observable.FromAsync(() => GetAccountsAsync())
    from account in accounts.ToObservable()
    from id in Observable.Defer(() => Observable.FromAsync(() => AddAccount(account))).Retry(3)
    select new { account, id };

它像LINQ一样懒惰地评估,因此要执行此操作,请执行以下操作:

IDisposable subscription =
    query
        .Subscribe(
            result => Console.WriteLine($"Account {result.account} created with id {result.id}"),
            ex => Console.WriteLine($"Exception {ex.GetType().FullName} with \"{ex.Message}\"."),
            () => Console.WriteLine("Completed Successfully"));

要在自然完成之前取消执行,只需调用subscription.Dispose()

以下是几个示例运行:

有错误

Account 40705 created with id 63
throwing on 40704!
Account 40701 created with id 21
Account 40702 created with id 21
Account 40703 created with id 27
throwing on 40704!
throwing on 40704!
Exception System.InvalidOperationException with "Account 40704 was not added".

成功完成

throwing on 40703!
throwing on 40702!
Account 40701 created with id 25
Account 40704 created with id 88
Account 40705 created with id 26
Account 40703 created with id 43
Account 40702 created with id 98
Completed Successfully

请注意,其中存在一些错误,但是.Retry(3)操作员只是尝试重试创建帐户并最终成功。

只需NuGet“ System.Reactive”并使用名称空间System.Reactive.Linq即可使其正常工作。