Как получить результаты при использовании Task.WhenAll - PullRequest
0 голосов
/ 09 февраля 2019

Предположим, у меня есть веб-сервис, который добавляет учетную запись.Я должен использовать этот сервис для добавления списка учетных записей:

"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;
    }
}

Я хочу получить идентификатор всех пяти учетных записей.

Как получить результат успешно выполненных задач в блоке try/catch?Я имею в виду в первом раунде, когда я жду allTasks, allTasks имеет статус Faulted, и я не могу получить возвращаемые значения для первых добавленных учетных записей.

1 Ответ

0 голосов
/ 10 февраля 2019

Вы должны рассмотреть 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, чтобы заставить это работать.

...