HttpClient 502 错误网关仅异步
HttpClient 502 Bad Gateway Async only
我在异步调用 Web 服务时遇到问题。我在一个循环中同步调用它数百次而没有错误,但是当我异步调用它时,我得到一个 502 Bad Gateway
错误,通常是在大约 250 次异步调用之后。如您所见,我已经尝试使用 Thread.Sleep()
等待 50 毫秒,然后每条记录最多重试 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; }
}
}
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 请求,然后移动到循环的下一次迭代并开始下一次。但这对于服务器来说可能仍然太快,因为在上一个请求完成之前您仍在发送所有请求。
您可能不得不限制您的请求。如果你想让它尽可能快,你可以找出网络服务允许你发送多少请求,你可以为网络服务定制你的代码。否则,你只是在猜测。
您可以一次发送 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
。
250 个并行调用太多了。它应该是 10-30。 C# Http 客户端的默认最大并发请求数是 10,因此如果您不覆盖该值,尝试同时打开超过 10 个套接字连接是没有意义的。另外,请记住,502 状态很可能是由反向代理返回的,这意味着您并没有真正访问网络 API,代理告诉您“放慢速度,我无法再处理了”。所以在你的情况下,这不是真正的 HttpClient
问题,否则,你会得到一些异常,比如 SocketException
由于套接字耗尽,你可能不会因为 HttpClient
限制并发到 10。另外,你有很多线程阻塞代码,比如 Thread.Sleep()
或 task.Wait()
,将你的主要方法签名更改为 static async Task Main()
并在任何地方使用 await
,否则,你会运行 进入其他类型的问题,例如线程耗尽
我在异步调用 Web 服务时遇到问题。我在一个循环中同步调用它数百次而没有错误,但是当我异步调用它时,我得到一个 502 Bad Gateway
错误,通常是在大约 250 次异步调用之后。如您所见,我已经尝试使用 Thread.Sleep()
等待 50 毫秒,然后每条记录最多重试 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; }
}
}
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 请求,然后移动到循环的下一次迭代并开始下一次。但这对于服务器来说可能仍然太快,因为在上一个请求完成之前您仍在发送所有请求。
您可能不得不限制您的请求。如果你想让它尽可能快,你可以找出网络服务允许你发送多少请求,你可以为网络服务定制你的代码。否则,你只是在猜测。
您可以一次发送 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
。
250 个并行调用太多了。它应该是 10-30。 C# Http 客户端的默认最大并发请求数是 10,因此如果您不覆盖该值,尝试同时打开超过 10 个套接字连接是没有意义的。另外,请记住,502 状态很可能是由反向代理返回的,这意味着您并没有真正访问网络 API,代理告诉您“放慢速度,我无法再处理了”。所以在你的情况下,这不是真正的 HttpClient
问题,否则,你会得到一些异常,比如 SocketException
由于套接字耗尽,你可能不会因为 HttpClient
限制并发到 10。另外,你有很多线程阻塞代码,比如 Thread.Sleep()
或 task.Wait()
,将你的主要方法签名更改为 static async Task Main()
并在任何地方使用 await
,否则,你会运行 进入其他类型的问题,例如线程耗尽