我正在使用.NET Framework 4.6.1。
我的Web API中有一个控制器,其中有静态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
的实现,我们可以看到它使用一个简单的Dictionary存储标头:
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
为null引起的。现在查看呼叫者: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
)并将标头复制到请求中。总结起来,与此同时,我们有一个线程迭代字典的内容,以及另一个添加/删除元素。这可能会导致您看到的行为。
要解决此问题,您有两种解决方案:
在静态构造函数中初始化
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
与您的自定义标头一起使用,而不是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
迭代137,值为null
重现此问题可能需要进行一些尝试,因为并发访问字典时线程倾向于陷入无限循环(如果发生在您的Web服务器上,则ASP.NET将在超时后中止线程)。
关于c# - 在异步方法中使用时,HttpClient header 将为空,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47973590/