在异步方法中使用时 HttpClient 标头被清空

2024-06-29

我正在使用 .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,我们可以看到它使用一个简单的字典来存储标题:

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)并将标头复制到请求中。

总结一下,同时我们有一个线程迭代字典的内容,另一个线程添加/删除元素。这可能会导致您所看到的行为。

要解决此问题,您有两种解决方案:

  1. 初始化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"));
    }
    
  2. 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 将在超时后中止线程)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在异步方法中使用时 HttpClient 标头被清空 的相关文章

随机推荐

  • 同心放射圆 d3

    我有一个等距值的数组 我用它来绘制同心圆 我想使用一种散发效果 本质上是 一旦最外面的圆的值超过最大值 就将其删除 并在中心添加一个新的圆来补偿 我不确定如何操作数据集来删除和添加新圆圈
  • Oracle 中的 if(条件, then, else)

    MySQL MSSQL 有一个简洁的小内联 if 函数 您可以在查询中使用它来检测空值 如下所示 SELECT foo a field AS a field SELECT if foo bar is null 0 foo bar AS ba
  • 用于按顺序批量重命名文件的 PowerShell 命令

    我正在尝试让最终用户更轻松地进行批处理文件命名 在拍摄外景时 我们有时只能拍摄 1 张照片或 500 张照片 具体取决于拍摄地点的大小 下面的代码可以很好地根据目录中的文件数量批量重命名我们的照片 prefix SomePrefix fil
  • 如何在带有参数的默认情况下针对枚举使用模式

    我有这个枚举 enum Foo MyFoo String YourFoo String HisFoo String TheirFoo Vec
  • 为什么视口宽度与实际显示宽度不匹配?

    Chrome 显示我的视口宽度为 1280px 然而 我的实际显示分辨率是2560x1600px 我使用的机器是13 3英寸的MacBook Pro 为什么视口不是 2560px 宽 使用没有任何区别 my display settings
  • 运行时两个注册之间的简单注入器基于动态上下文的注入

    我有一个使用 Simple Injector 进行命令处理程序注册的中介应用程序 并且注入和处理程序均已设置并完美运行 class DoWashingCommandHandler IRequestHandler
  • 如何使 TextField 右对齐(尾随)

    我正在努力拥有一个价值文本域以尾随对齐方式显示 正如你所看到的价值34 3以前导对齐方式显示 我确信我错过了一些明显的东西 但我不知道是什么 有任何想法吗 State private var endwert 34 3 var numberF
  • 如何让 takeLatest 考虑 url、参数和方法?

    我用的是佐贺的takeLatest中止除最新请求之外的所有请求 这工作正常 但现在我只想中止请求don t具有相同的 url 参数和方法 我知道佐贺使用type属性来比较操作 就像香草 Redux 一样 但我还添加了url params a
  • 如何在单击时使图像抖动/摆动?

    我有一个图像按钮 我想在触摸它时摇动 摆动 我希望它能够像 iPhone 应用程序图标在被长时间按下时那样摆动 Thanks 尝试使用这个
  • 如何查找列表中元素的索引?

    给定列表中的一个元素 我可以使用哪个函数来查找其索引 例如 我想在列表中找到 3 的索引 1 2 3 4 Haskell 中有哪个函数可以用于此目的 看看这里 在 Haskell 中查找列表中元素的索引 https stackoverflo
  • Gnuplot:如何跳过矩阵输入中的列进行绘图?

    我有以下形式的数据文件 unimportant1 unimportant2 unimportant3 matrixdata i 1e4 2e5 3e2 1 2 3 4 5 2e3 1e1 7e3 5 4 3 2 1 2e3 1e4 4e2
  • Javascript CORS 图像/画布操作

    我正在尝试从另一个已配置为允许 CORS 的域检索图像 并操纵像素 然后我想显示结果并能够操纵结果 我可以在我请求的图像上使用 getImageData 和 toDataURL 所以我知道服务器部分可以工作 但是 当我尝试将图像的 src
  • 如何使用 Amazon S3 SDK 更新元数据

    我正在使用 Amazon 的 AWS SDK 的 PHP 版本 我有一堆带有Expires标头 我想删除该标头并添加一个Cache control标题代替 这更新对象 http docs amazonwebservices com AWSS
  • 如何从 std::vector 中删除元素而不调整其大小

    迭代器擦除 迭代器位置 迭代器擦除 首先是迭代器 迭代器最后 擦除元素 从向量中删除 容器可以是单个元素 位置 或一系列元素 第一个 最后一个 这有效地减少了向量 大小除以元素数量 删除 调用每个元素的 之前的析构函数 and remove
  • Spree商店支持多个供应商购买吗?

    您好 我需要一个商店软件 我可以在其中提供来自不同供应商的产品 例如人们可以从 3 个不同的供应商处以不同的价格购买一块手表 亚马逊也这样做 看 Spree 有没有类似的插件 是的 spree 有 spree 多供应商 spree 多商店以
  • Azure 服务总线:什么是“请求”和“消息”?

    在 Microsoft Azure 中 在服务总线下 您可以看到不同队列 主题等的活动图 该图显示了各种不同的线 例如收到的消息 传出消息 成功请求 etc 什么是Request 什么是Message 请求似乎不断发生 并且通常远远大于消息
  • 通过sql视图向多个表插入数据

    mysql 有没有办法通过视图向多个表插入数据 MySQL 参考手册对于可更新视图是这样说的 一些视图是可更新的 也就是说 您可以在诸如以下的语句中使用它们UPDATE DELETE or INSERT更新基础表的内容 为了使视图可更新 必
  • PHP、MySQL 验证故障且搜索不起作用?

    我创建了一个小的注册粘性表格 一切工作正常 但如果我输入任何错误的值 例如姓名中的数字 年龄中的字母甚至错误的电子邮件格式 那么数据仍然保存在数据库中 我无法找出验证问题 另外一个是搜索选项 每当我在搜索框中输入任何名字或姓氏时 它都应该显
  • 使用 Hibernate/Spring 生成数据库更新脚本

    我有一个项目 我们过去依赖 hibernate 来更新数据库 hibernate hbm2ddl auto update 即使在产品上 我正在将其迁移为使用 liquibase 我唯一担心的是 并不是我的团队中的每个人都是 sql 专家 因
  • 在异步方法中使用时 HttpClient 标头被清空

    我正在使用 NET Framework 4 6 1 我的 Web api 中有一个控制器 其中有静态 HttpClient 来处理所有 http 请求 在 IIS 上托管我的应用程序后 大约每月一次 我的应用程序的所有传入请求都会出现以下异