多个异步调用,如何以有意义的方式处理响应
Multiple Async calls, how to deal with responses in a way that make sense
我正在对几个不同的 URL 进行多次异步调用,两个 url 应该 return 相同的结果,但我想比较两者的结果或检查中的某些值回应。我不确定如何在状态代码之外的响应中比较或查找特定值,有没有一种简单的方法可以做到这一点?还想注意响应,如果它失败了,我希望能够在我的代码中跟踪它,以便不再使用 url,我不确定我将如何处理这个
代码:
private async Task<ClientModel> getClientInfoAsync(string clientID)
{
ClientModel c = null;
try
{
var client = new HttpClient();
//Start requests for all of them
var requests = urls.Select
(
url => client.GetAsync(getURL(url, "Client", clientID))
).ToList();
//Wait for all the requests to finish
await Task.WhenAll(requests);
//Get the responses
var responses = requests.Select
(
task => task.Result
);
foreach (var r in responses)
{
// Extract the message body
var s = await r.Content.ReadAsStringAsync();
if (r.IsSuccessStatusCode)
{
c = r.Content.ReadAsAsync<ClientModel>().Result;
SetLastSuccessfulCommunicationDetails(); //after this call HERE I THINK IS WHERE I WOULD COMPARE RESPONSES AND GO FROM THERE
}
}
}
catch (Exception ex)
{
string errMsg = "Error getting the client info";
//...catch error code here...
}
return c;
}
基本上我不确定如何处理响应,根据我的比较和响应状态,只有 return 一个客户端模型 (c)。让我知道是否需要包含任何进一步的信息
首先,如果可以的话,请不要使用.Result
。有一种更简洁的方式来获取所有回复。
var responses = await Task.WhenAll(requests);
该方法的一个缺点是,如果任何请求抛出异常,它就会抛出异常。所以如果你想智能地处理这种情况,你需要 try/catch 并有一些特殊的逻辑来处理它。
您可以为每个异步步骤继续该模式,以将结果作为集合处理:
var validResponses = responses.Where(r =>r.IsSuccessStatusCode);
var clientModels = await Task.WhenAll(
validResponses
.Select(async r => await r.Content.ReadAsAsync<ClientModel>()));
这种方法的一个缺点是您最终会等待所有请求完成,然后才能开始阅读任何响应内容。因此,将发出请求、获取响应和沿途收集任何数据的整个过程放在一个异步方法中,并且只说 var responseData = await Task.WhenAll(urls.Select(ThatMethod))
可能更有意义。这样,只要有一个请求返回,您就可以开始使用它的响应。
在比较结果时,如果不知道您正在寻找哪种比较,就无法提供帮助。但是,假设您想要模型中某些 属性 具有最高值的那个,例如:
var lastResponseModel = clientModels
.OrderByDescending(c => c.LastSaved)
.First();
return lastResponseModel;
如果我可以假设您有一个如下所示的方法:
private Task<ClientModel> DetermineClientModelFromResponses(IEnumerable<string> responses)
...然后您可以使用 Microsoft 的 Reactive Framework(又名 Rx)- NuGet System.Reactive
并添加 using System.Reactive.Linq;
.
它让你这样做:
private async Task<ClientModel> GetClientInfoAsync(string clientID) =>
await DetermineClientModelFromResponses(
await Observable.Using(
() => new HttpClient(),
client =>
urls
.ToObservable()
.SelectMany(url => Observable.FromAsync(() => client.GetAsync(getURL(url, "Client", clientID))))
.Where(response => response.IsSuccessStatusCode)
.SelectMany(response => Observable.FromAsync(() => response.Content.ReadAsStringAsync()))
.ToArray()));
...或者,或者,这个:
private async Task<ClientModel> GetClientInfoAsync(string clientID) =>
await DetermineClientModelFromResponses(
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.FromAsync(() => client.GetAsync(getURL(url, "Client", clientID)))
where response.IsSuccessStatusCode
from text in Observable.FromAsync(() => response.Content.ReadAsStringAsync())
select text
).ToArray()));
如果您对第一次成功响应获胜感到满意,那么这应该适合您,但您需要确保至少有一个成功:
private async Task<ClientModel> GetClientInfoAsync(string clientID) =>
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.FromAsync(() => client.GetAsync(getURL(url, "Client", clientID)))
where response.IsSuccessStatusCode
from text in Observable.FromAsync(() => response.Content.ReadAsAsync<ClientModel>())
select text
).Take(1));
为了使其对错误更加稳健,您有一些策略。
我改造了你的代码来做一个简单的例子:
async Task Main()
{
var result = (string)"no result";
try
{
result = await GetClientInfoAsync("123");
}
catch (NotImplementedException ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(result);
}
private List<string> urls = new List<string>() { "Hello" };
private async Task<string> GetClientInfoAsync(string clientID) =>
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.FromAsync(() => Test1(url))
from text in Observable.FromAsync(() => Test2(response))
select $"{clientID}:{text}"
)
.Concat(Observable.Return<string>(null))
.Take(1));
private Random _random = new Random();
Task<string> Test1(string url)
{
if (_random.NextDouble() > 0.3)
{
throw new NotImplementedException("Test1!");
}
return Task.Run(() => $"{url}!");
}
Task<string> Test2(string response)
{
if (_random.NextDouble() > 0.3)
{
throw new NotImplementedException("Test2!");
}
return Task.Run(() => $"{response}#");
}
此代码将在出现异常时立即结束 GetClientInfoAsync
,并让它冒泡到 Main
方法。这对你来说可能不够。
一种替代方法是向每个 Test1
和 Test2
添加正常的 try
/catch
代码,以确保它们永远不会失败。
或者,您可以很容易地添加“重试”功能。
private async Task<string> GetClientInfoAsync(string clientID) =>
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.Defer(() => Observable.FromAsync(() => Test1(url))).Retry(5)
from text in Observable.Defer(() => Observable.FromAsync(() => Test2(response))).Retry(5)
select $"{clientID}:{text}"
)
.Concat(Observable.Return<string>(null))
.Take(1));
请注意,现在 Test1
和 Test2
各重试 5 次。
错误仍然有可能通过,但这是正常的编码,对吗?
请注意,我还添加了 .Concat(Observable.Return<string>(null))
以确保在没有值来自查询本身时查询生成一个值。 Concat
在连接 null
结果之前等待主查询结束,因此如果主查询没有产生任何值,那么 null
将出来。
我正在对几个不同的 URL 进行多次异步调用,两个 url 应该 return 相同的结果,但我想比较两者的结果或检查中的某些值回应。我不确定如何在状态代码之外的响应中比较或查找特定值,有没有一种简单的方法可以做到这一点?还想注意响应,如果它失败了,我希望能够在我的代码中跟踪它,以便不再使用 url,我不确定我将如何处理这个
代码:
private async Task<ClientModel> getClientInfoAsync(string clientID)
{
ClientModel c = null;
try
{
var client = new HttpClient();
//Start requests for all of them
var requests = urls.Select
(
url => client.GetAsync(getURL(url, "Client", clientID))
).ToList();
//Wait for all the requests to finish
await Task.WhenAll(requests);
//Get the responses
var responses = requests.Select
(
task => task.Result
);
foreach (var r in responses)
{
// Extract the message body
var s = await r.Content.ReadAsStringAsync();
if (r.IsSuccessStatusCode)
{
c = r.Content.ReadAsAsync<ClientModel>().Result;
SetLastSuccessfulCommunicationDetails(); //after this call HERE I THINK IS WHERE I WOULD COMPARE RESPONSES AND GO FROM THERE
}
}
}
catch (Exception ex)
{
string errMsg = "Error getting the client info";
//...catch error code here...
}
return c;
}
基本上我不确定如何处理响应,根据我的比较和响应状态,只有 return 一个客户端模型 (c)。让我知道是否需要包含任何进一步的信息
首先,如果可以的话,请不要使用.Result
。有一种更简洁的方式来获取所有回复。
var responses = await Task.WhenAll(requests);
该方法的一个缺点是,如果任何请求抛出异常,它就会抛出异常。所以如果你想智能地处理这种情况,你需要 try/catch 并有一些特殊的逻辑来处理它。
您可以为每个异步步骤继续该模式,以将结果作为集合处理:
var validResponses = responses.Where(r =>r.IsSuccessStatusCode);
var clientModels = await Task.WhenAll(
validResponses
.Select(async r => await r.Content.ReadAsAsync<ClientModel>()));
这种方法的一个缺点是您最终会等待所有请求完成,然后才能开始阅读任何响应内容。因此,将发出请求、获取响应和沿途收集任何数据的整个过程放在一个异步方法中,并且只说 var responseData = await Task.WhenAll(urls.Select(ThatMethod))
可能更有意义。这样,只要有一个请求返回,您就可以开始使用它的响应。
在比较结果时,如果不知道您正在寻找哪种比较,就无法提供帮助。但是,假设您想要模型中某些 属性 具有最高值的那个,例如:
var lastResponseModel = clientModels
.OrderByDescending(c => c.LastSaved)
.First();
return lastResponseModel;
如果我可以假设您有一个如下所示的方法:
private Task<ClientModel> DetermineClientModelFromResponses(IEnumerable<string> responses)
...然后您可以使用 Microsoft 的 Reactive Framework(又名 Rx)- NuGet System.Reactive
并添加 using System.Reactive.Linq;
.
它让你这样做:
private async Task<ClientModel> GetClientInfoAsync(string clientID) =>
await DetermineClientModelFromResponses(
await Observable.Using(
() => new HttpClient(),
client =>
urls
.ToObservable()
.SelectMany(url => Observable.FromAsync(() => client.GetAsync(getURL(url, "Client", clientID))))
.Where(response => response.IsSuccessStatusCode)
.SelectMany(response => Observable.FromAsync(() => response.Content.ReadAsStringAsync()))
.ToArray()));
...或者,或者,这个:
private async Task<ClientModel> GetClientInfoAsync(string clientID) =>
await DetermineClientModelFromResponses(
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.FromAsync(() => client.GetAsync(getURL(url, "Client", clientID)))
where response.IsSuccessStatusCode
from text in Observable.FromAsync(() => response.Content.ReadAsStringAsync())
select text
).ToArray()));
如果您对第一次成功响应获胜感到满意,那么这应该适合您,但您需要确保至少有一个成功:
private async Task<ClientModel> GetClientInfoAsync(string clientID) =>
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.FromAsync(() => client.GetAsync(getURL(url, "Client", clientID)))
where response.IsSuccessStatusCode
from text in Observable.FromAsync(() => response.Content.ReadAsAsync<ClientModel>())
select text
).Take(1));
为了使其对错误更加稳健,您有一些策略。
我改造了你的代码来做一个简单的例子:
async Task Main()
{
var result = (string)"no result";
try
{
result = await GetClientInfoAsync("123");
}
catch (NotImplementedException ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(result);
}
private List<string> urls = new List<string>() { "Hello" };
private async Task<string> GetClientInfoAsync(string clientID) =>
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.FromAsync(() => Test1(url))
from text in Observable.FromAsync(() => Test2(response))
select $"{clientID}:{text}"
)
.Concat(Observable.Return<string>(null))
.Take(1));
private Random _random = new Random();
Task<string> Test1(string url)
{
if (_random.NextDouble() > 0.3)
{
throw new NotImplementedException("Test1!");
}
return Task.Run(() => $"{url}!");
}
Task<string> Test2(string response)
{
if (_random.NextDouble() > 0.3)
{
throw new NotImplementedException("Test2!");
}
return Task.Run(() => $"{response}#");
}
此代码将在出现异常时立即结束 GetClientInfoAsync
,并让它冒泡到 Main
方法。这对你来说可能不够。
一种替代方法是向每个 Test1
和 Test2
添加正常的 try
/catch
代码,以确保它们永远不会失败。
或者,您可以很容易地添加“重试”功能。
private async Task<string> GetClientInfoAsync(string clientID) =>
await Observable.Using(
() => new HttpClient(),
client =>
(
from url in urls.ToObservable()
from response in Observable.Defer(() => Observable.FromAsync(() => Test1(url))).Retry(5)
from text in Observable.Defer(() => Observable.FromAsync(() => Test2(response))).Retry(5)
select $"{clientID}:{text}"
)
.Concat(Observable.Return<string>(null))
.Take(1));
请注意,现在 Test1
和 Test2
各重试 5 次。
错误仍然有可能通过,但这是正常的编码,对吗?
请注意,我还添加了 .Concat(Observable.Return<string>(null))
以确保在没有值来自查询本身时查询生成一个值。 Concat
在连接 null
结果之前等待主查询结束,因此如果主查询没有产生任何值,那么 null
将出来。