我正在对几个不同的 URL 进行多个异步调用,两个 URL 应该返回相同的结果,但我想比较两个 URL 的结果或检查响应中的某些值。我不确定如何比较或查找状态代码之外的响应中的特定值,有没有一种简单的方法可以做到这一点?还想记下响应,如果失败,我希望能够在以后的代码中跟踪该响应,以便不再使用该 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;
}
基本上我不确定如何处理响应,并且根据我的比较和响应状态仅返回一个客户端模型 (c)。如果我需要提供任何进一步的信息,请告诉我。
如果我可以假设你有一个如下所示的方法:
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
方法。这对你来说可能还不够。
一种替代方法是向每个
try
和 catch
添加正常的 Test1
/Test2
代码,以确保它们永远不会失败。
或者,您可以非常轻松地添加“重试”功能。
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
将出现。
首先,如果可以的话,不要使用
.Result
。有一种更干净的方式来获取所有回复。
var responses = await Task.WhenAll(requests);
该方法的一个缺点是,如果任何请求抛出异常,它就会抛出异常。因此,如果您想智能地处理这种情况,您需要尝试/捕获并有一些特殊的逻辑来处理该情况。
您可以为每个异步步骤继续该模式,以将结果作为集合处理:
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;