我正在使用 .NET Framework 4.6.1。
我的 web api 中有一个 Controller ,其中我有静态 HttpClient 来处理所有 http 请求。在 IIS 上托管我的应用程序后,大约每月一次,我的应用程序的所有传入请求都会出现以下异常:
System.ArgumentNullException: Value cannot be null.
at System.Threading.Monitor.Enter(Object obj)
at System.Net.Http.Headers.HttpHeaders.ParseRawHeaderValues(String name, HeaderStoreItemInfo info, Boolean removeEmptyHeader)
at System.Net.Http.Headers.HttpHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.Headers.HttpRequestHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.HttpClient.PrepareRequestMessage(HttpRequestMessage request)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
at Attributes.Controllers.AttributesBaseController.<UpdateAttributes>d__6.MoveNext() in D:\Git\PortalSystem\Attributes\Controllers\AttributesBaseController.cs:line 42
如果我在 IIS 上重新启动应用程序池,一切都会重新开始正常工作。这是我的代码:
public class AttributesBaseController : ApiController
{
[Inject]
public IPortalsRepository PortalsRepository { get; set; }
private static HttpClient Client = new HttpClient(new HttpClientHandler { Proxy = null, UseProxy = false })
{ Timeout = TimeSpan.FromSeconds(double.Parse(WebConfigurationManager.AppSettings["httpTimeout"])) };
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
protected async Task UpdateAttributes(int clientId, int? updateAttrId = null)
{
try
{
Client.DefaultRequestHeaders.Accept.Clear();
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
#region Update Client Dossier !!! BELOW IS LINE 42 !!!!
using (var response = await Client.PutAsync(new Uri(WebConfigurationManager.AppSettings["dossier"] + "api/dossier?clientId=" + clientId), null))
{
if (!response.IsSuccessStatusCode)
{
logger.Error($"Dossier update failed");
}
}
#endregion
#region Gather Initial Info
var checkSystems = PortalsRepository.GetCheckSystems(clientId);
var currentAttributes = PortalsRepository.GetCurrentAttributes(clientId, checkSystems);
#endregion
List<Task> tasks = new List<Task>();
#region Initialize Tasks
foreach (var cs in checkSystems)
{
if (!string.IsNullOrEmpty(cs.KeyValue))
{
tasks.Add(Task.Run(async () =>
{
var passedAttributes = currentAttributes.Where(ca => ca.SystemId == cs.SystemId && ca.AttributeId == cs.AttributeId &&
(ca.SysClientId == cs.KeyValue || ca.OwnerSysClientId == cs.KeyValue)).ToList();
if (cs.AttributeId == 2 && (updateAttrId == null || updateAttrId == 2))
{
await UpdateOpenWayIndividualCardsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 3 && (updateAttrId == null || updateAttrId == 3))
{
await UpdateEquationAccountsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 8 && (updateAttrId == null || updateAttrId == 8))
{
await UpdateOpenWayCorporateInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 9 && (updateAttrId == null || updateAttrId == 9))
{
await UpdateEquationDealsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 10 && (updateAttrId == null || updateAttrId == 10))
{
await UpdateOpenWayIndividualCardDepositsInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 16 && (updateAttrId == null || updateAttrId == 16))
{
await UpdateOpenWayBonusInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 17 && (/*updateAttrId == null ||*/ updateAttrId == 17))
{
await UpdateExternalCardsInfo(passedAttributes, cs, clientId);
}
if (cs.AttributeId == 18 && (updateAttrId == null || updateAttrId == 18))
{
await UpdateCRSInfo(passedAttributes, cs, clientId);
}
else if (cs.AttributeId == 22 && (updateAttrId == null || updateAttrId == 22))
{
await UpdateCardInsuranceInfo(passedAttributes, cs, clientId);
}
}));
}
}
#endregion
// Run all tasks
await Task.WhenAny(Task.WhenAll(tasks.ToArray()), Task.Delay(TimeSpan.FromSeconds(double.Parse(WebConfigurationManager.AppSettings["taskWaitTime"]))));
}
catch (Exception ex)
{
logger.Error(ex);
}
}
}
任何人都可以给我建议/帮助解决问题吗?我只是不知道问题是出在我将 HttpClient 用于任务的方式上,还是出在 IIS 上。
最佳答案
查看 DefaultRequestHeaders
的实现,我们可以看到它使用一个简单的字典来存储 header :
private Dictionary<string, HttpHeaders.HeaderStoreItemInfo> headerStore;
DefaultRequestHeaders.Accept.Clear
只是从字典中删除键,没有任何同步:
public bool Remove(string name)
{
this.CheckHeaderName(name);
if (this.headerStore == null)
return false;
return this.headerStore.Remove(name);
}
Dictionary.Remove
不是线程安全的,如果您在此操作期间访问字典,可能会发生不可预测的行为。
现在,如果我们查看堆栈跟踪中的 ParseRawHeaderValues
方法:
private bool ParseRawHeaderValues(string name, HttpHeaders.HeaderStoreItemInfo info, bool removeEmptyHeader)
{
lock (info)
{
// stuff
}
return true;
}
我们可以看到错误是由info
为空引起的。现在看看调用者:
internal virtual void AddHeaders(HttpHeaders sourceHeaders)
{
if (sourceHeaders.headerStore == null)
return;
List<string> stringList = (List<string>) null;
foreach (KeyValuePair<string, HttpHeaders.HeaderStoreItemInfo> keyValuePair in sourceHeaders.headerStore)
{
if (this.headerStore == null || !this.headerStore.ContainsKey(keyValuePair.Key))
{
HttpHeaders.HeaderStoreItemInfo headerStoreItemInfo = keyValuePair.Value;
if (!sourceHeaders.ParseRawHeaderValues(keyValuePair.Key, headerStoreItemInfo, false))
{
if (stringList == null)
stringList = new List<string>();
stringList.Add(keyValuePair.Key);
}
else
this.AddHeaderInfo(keyValuePair.Key, headerStoreItemInfo);
}
}
if (stringList == null)
return;
foreach (string key in stringList)
sourceHeaders.headerStore.Remove(key);
}
长话短说,我们迭代 DefaultRequestHeaders
中的字典(即 sourceHeaders.headerStore
)并将 header 复制到请求中。
总结一下,我们同时有一个线程迭代字典的内容,另一个添加/删除元素。这可能会导致您所看到的行为。
要解决这个问题,您有两种解决方案:
在静态构造函数中初始化
DefaultRequestHeaders
,然后永远不要更改它:static AttributesBaseController { Client = new HttpClient(new HttpClientHandler { Proxy = null, UseProxy = false }) { Timeout = TimeSpan.FromSeconds(double.Parse(WebConfigurationManager.AppSettings["httpTimeout"])) }; Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }
将
SendAsync
与您的自定义 header 一起使用,而不是PutAsync
:var message = new HttpRequestMessage(HttpMethod.Put, new Uri(WebConfigurationManager.AppSettings["dossier"] + "api/dossier?clientId=" + clientId)); message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); using (var response = await Client.SendAsync(message)) { // ... }
只是为了好玩,一个小复制:
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var storeField = typeof(HttpHeaders).GetField("headerStore", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo valueField = null;
var store = (IEnumerable)storeField.GetValue(client.DefaultRequestHeaders);
foreach (var item in store)
{
valueField = item.GetType().GetField("value", BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine(valueField.GetValue(item));
}
for (int i = 0; i < 8; i++)
{
Task.Run(() =>
{
int iteration = 0;
while (true)
{
iteration++;
try
{
foreach (var item in store)
{
var value = valueField.GetValue(item);
if (value == null)
{
Console.WriteLine("Iteration {0}, value is null", iteration);
}
break;
}
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
catch (Exception) { }
}
});
}
Console.ReadLine();
输出:
System.Net.Http.Headers.HttpHeaders+HeaderStoreItemInfo
Iteration 137, value is null
重现该问题可能需要多次尝试,因为线程在并发访问字典时往往会陷入无限循环(如果它发生在您的网络服务器上,ASP.NET 将在超时结束后中止该线程)。
关于c# - 在异步方法中使用时,HttpClient header 变为空,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47973590/