使用 Task.WhenAll 时如何获得结果
How to get results when use Task.WhenAll
假设我有一个添加帐户的网络服务。我应该使用此服务来添加帐户列表:
"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
状态,我无法获得第一个添加帐户的 return 值。
您应该为此考虑 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
使其正常工作。
假设我有一个添加帐户的网络服务。我应该使用此服务来添加帐户列表:
"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
状态,我无法获得第一个添加帐户的 return 值。
您应该为此考虑 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
使其正常工作。