看看实施情况DefaultRequestHeaders
,我们可以看到它使用一个简单的字典来存储标题:
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
)并将标头复制到请求中。
总结一下,同时我们有一个线程迭代字典的内容,另一个线程添加/删除元素。这可能会导致您所看到的行为。
要解决此问题,您有两种解决方案:
-
初始化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"));
}
-
Use 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();
Output:
System.Net.Http.Headers.HttpHeaders+HeaderStoreItemInfo
迭代 137,值为 null
重现该问题可能需要几次尝试,因为并发访问字典时线程往往会陷入无限循环(如果发生在您的 Web 服务器上,ASP.NET 将在超时后中止线程)。