我在异步调用web服务时遇到了问题。我在循环中同步调用了它数百次,但是当我异步调用它时,我会得到一个502 Bad Gateway
错误,通常是在大约250个异步调用之后。正如您所看到的,我尝试过用Thread.Sleep()
等待50 As,每次重试5次。当我无法验证记录时,预期会出现404 Not Found
错误。我的框架是netstandard2.0。
class Program
{
static void Main(string[] args)
{
List<OriginalRecord> originalRecords = new List<OriginalRecord>();
originalRecords.Add(new OriginalRecord()
{
Id = "IdOne",
Foo = "fooOne"
});
originalRecords.Add(new OriginalRecord()
{
Id = "IdTwo",
Foo = "fooTwo"
});
List<RecordValidation> unvalidatedRecords = new List<RecordValidation>();
try
{
RecordValidator validator = new RecordValidator("myAccessToken", "myBarId");
var task = validator.ValidateRecordsAsync(originalRecords.Select(r => r.Id).ToList());
task.Wait();
unvalidatedRecords = task.Result;
}
catch
{
throw;
}
}
}
public class RecordValidator
{
public RecordValidator(string AccessToken, string BarId)
{
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("MessageService/3.1");
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken);
httpClient.DefaultRequestHeaders.Add("Accept", "application/hal+json");
httpClient.DefaultRequestHeaders.ExpectContinue = false;
barId = BarId;
}
private static HttpClient httpClient { get; set; }
public string barId { get; set; }
public async Task<List<RecordValidation>> ValidateRecordsAsync(List<string> Records)
{
List<Task<RecordValidation>> tasks = new List<Task<RecordValidation>>();
foreach (var record in Records)
{
tasks.Add(Task.Run(() => ValidateRecordAsync(record)));
}
var results = await Task.WhenAll(tasks);
return results.Where(r => !r.Validated).ToList();
}
private async Task<RecordValidation> ValidateRecordAsync(string Record)
{
string url = $"https://data.somewebapp.com/api/bar/{barId}/record/{Uri.EscapeDataString(Record)}";
int retries = 0;
bool success = false;
HttpResponseMessage response = new HttpResponseMessage();
try
{
do
{
response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
success = true;
}
else
{
Thread.Sleep(50);
retries++;
}
} while (retries < 5 && !success);
response.EnsureSuccessStatusCode();
string responseString = await response.Content.ReadAsStringAsync();
var output = JsonConvert.DeserializeObject<RecordValidation>(responseString);
if (!string.IsNullOrEmpty(output.RecordId))
output.Validated = true;
return output;
}
catch (HttpRequestException ex) when (ex.Message == "Response status code does not indicate success: 404 (Not Found).")
{
return new RecordValidation()
{
RecordId = Record,
Validated = false
};
}
catch
{
throw;
}
}
}
public class RecordValidation
{
public RecordValidation()
{
RecordId = string.Empty;
Validated = false;
}
[JsonProperty("id")]
public string RecordId { get; set; }
public bool Validated { get; set; }
}
public class OriginalRecord
{
public string Id { get; set; }
public string Foo { get; set; }
}
}
发布于 2022-03-05 00:29:14
502响应是实际响应。HTTP请求成功,服务器返回502响应。这很可能是因为你击中服务器的速度太快,太难了,而且它把你切断了。
当您同步执行时,它将发送请求并等待响应,然后再转到下一个响应。
但在这个循环中:
foreach (var record in Records)
{
tasks.Add(Task.Run(() => ValidateRecordAsync(record)));
}
您正在为每个请求创建一个新线程,其效果是几乎同时发送每个请求。显然,服务器不喜欢这样做。
您不需要为每个请求创建一个新线程。您可以在没有Task.Run()
的情况下异步启动它们,例如:
foreach (var record in Records)
{
tasks.Add(ValidateRecordAsync(record));
}
这只会稍微减缓它的速度,但不会太大。这将在同一个线程上运行ValidateRecordAsync()
,直到在进入下一个循环迭代并启动下一个循环之前发送HTTP请求。但是对于服务器来说,这可能还是太快了,因为在前面的请求完成之前,您仍然在发送所有的请求。
你很可能不得不抑制你的请求。如果您希望它尽可能快,您可以知道web服务将允许您发送多少请求,您可以根据web服务定制您的代码。否则,你就只能猜测了。
您可以发送,例如每次10,并在中间添加一个暂停。
foreach (var record in Records)
{
tasks.Add(ValidateRecordAsync(record));
if (tasks.Count % 10 == 0) await Task.Delay(100);
}
或者等到这10个人完成后再继续前进:
foreach (var record in Records)
{
tasks.Add(ValidateRecordAsync(record));
if (tasks.Count % 10 == 0) await Task.WhenAll(tasks);
}
您可以在同一任务列表上多次调用Task.WhenAll
。
发布于 2022-03-07 16:44:07
250个并行电话太多了。应该是10-30。对于C# Http客户端的默认最大并发请求数为10,因此如果您没有覆盖该值,那么尝试同时打开10个以上的套接字连接是没有意义的。另外,请记住,502状态很可能是由反向代理返回的,这意味着您没有真正地访问web,代理告诉您“慢下来,我不能再处理了”。因此,在您的情况下,这并不是真正的HttpClient
问题,否则,您将得到一些异常,比如套接字耗尽导致的SocketException
,由于HttpClient
将并发性限制为10,您可能不会得到这些异常。此外,您有大量的线程阻塞代码,如Thread.Sleep()
或task.Wait()
,将您的主要方法签名更改为static async Task Main()
并在任何地方使用await
,否则,您将遇到其他类型的问题,如线程耗尽。
https://stackoverflow.com/questions/71357621
复制相似问题