在使用异步方法时,当使用HttpClient时,标头会被设置为null。

6 浏览
0 Comments

在使用异步方法时,当使用HttpClient时,标头会被设置为null。

我正在使用.NET Framework 4.6.1。

我在我的Web API中有一个控制器,其中我有一个静态的HttpClient来处理所有的HTTP请求。在我将我的应用程序托管在IIS上之后,大约每个月一次,我会对我的应用程序的所有传入请求都收到以下异常:

System.ArgumentNullException: 值不能为空。
   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.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 tasks = new List();
            #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上发生了一些错误。

0
0 Comments

从上面的内容可以看出,HttpClient在使用异步方法时,使用DefaultRequestHeaders时会出现头部被置空的问题。这个问题的出现是因为在DefaultRequestHeaders的实现中,它使用了一个简单的字典来存储头部信息,而字典的Remove方法并不是线程安全的。当我们在迭代字典的同时,又有其他线程在添加/删除元素时,就会导致头部信息被置空的情况发生。

要解决这个问题,有两个解决方法:

  1. 在静态构造函数中初始化DefaultRequestHeaders,然后不再对它进行修改。
  2. 使用SendAsync方法发送请求,并在自定义的请求头中添加头部信息,而不是使用PutAsync方法。

在上面的内容中还提供了一个小的复现代码,用来演示这个问题的产生。通过多次运行这段代码,就可以看到线程在并发访问字典时可能会陷入无限循环的情况。

,通过修改代码,使用适当的方法来处理头部信息,就可以解决这个问题。在每个请求中添加特定的头部信息应该在HttpRequestMessage中完成,而不是在HttpClientDefaultRequestHeaders中进行修改。

感谢提供详细的解释。我已经修改了代码并在服务器上更新了应用程序。现在,我将监视它的行为。

这样修复了吗?

0