【问题标题】:Null reference in async loop, but object has no nulls异步循环中的空引用,但对象没有空值
【发布时间】:2019-11-10 00:07:49
【问题描述】:

首先,this SO post 没有回答这个问题,因为它描述了简单/基本的对象引用问题。我遇到的问题与多线程异步处理有关,其他帖子没有解决。

我有一个多线程的 .NET winforms 应用程序,我正在这样做:

if ( paramList != null ) {
   lock ( paramList ) {
      foreach ( DictionaryEntry param in paramList ) {
         command.Parameters.AddWithValue(param.Key.ToString(), param.Value);
      }
   }
}

paramList 是一个OrderedDictionary

我偶尔会在foreach 行收到此错误:

对象引用未设置为对象的实例。

如您所见,param.Key 为空,param.Value 为空。但这没有任何意义,因为paramList 中没有空值,您可以在此处看到:

在屏幕截图中,您只能看到索引 2,但我也检查了索引 0 和 1,同样的,有效数据,没有空值。

我没有使用多线程应用程序的经验,但由于this SO post 中的响应,我将该块放在了lock() 中。在放入 lock() 之前,我偶尔会收到错误 Collection was modified; enumeration operation may not execute. 放入锁后,该错误消失了,但现在我得到了如上所示的对象引用。

我能做些什么来解决这个问题?

编辑

听取了几位海报的建议,我这样做了:

private static object syncLock = new object();

再往下使用:

lock ( syncLock ) {
   if ( paramList != null ) {
       foreach ( DictionaryEntry param in paramList ) {
          command.Parameters.AddWithValue(param.Key.ToString(), param.Value);
       }
   }
}

这似乎解决了对象引用错误(谢谢大家),但现在我偶尔得到:

集合已修改;枚举操作可能无法执行。

因为我在尝试这种新方法后遇到了一个完全不同的错误,所以我created a new SO question。我不确定这样做是否正确,因为现在看来这些问题是相关的,而我只是看到同一个核心问题的不同症状。

如果有人有想法,仍在寻找解决方案。

【问题讨论】:

  • 尝试锁定不同的对象而不是 paramList 参见示例:docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
  • 通常你想要一个:私有只读对象 myLock = new object(),如果它是你试图保护的类变量。否则使锁为静态。第二:在 AddWithValue 之前测试 null。第三,您需要在访问参数列表的任何地方都使用锁,如果您在此处锁定,但不要锁定在添加或删除某些内容的另一个位置,那么您会收到此错误。
  • 我认为您正处于一个痛苦的世界,因为您在对概念没有扎实理解的情况下开始使用多线程。如果您有时间阅读,这是一个很好的资源:@​​987654328@,作者 Joseph Albahari。

标签: c# multithreading thread-safety


【解决方案1】:

(首选)更改您的paramList,这样您就不需要锁定它。

如果你需要分享,那么:

  1. 锁定不变的对象,例如private readonly object l = new object();private static readonly(如果需要)。
  2. 检查填写paramList的代码也是locked。

【讨论】:

  • 我真的很困惑为什么这被否决了。我什至不是海报!
  • 我对你的建议感到困惑。您能否根据我发布的代码提供示例代码?
  • @HerrimanCoder:这是不受欢迎的,我不喜欢复制其他海报作品,但这与我所说的基本相同 - 使用锁定。不过,我确实添加了一些链接和更多解释。所以我觉得还可以。
【解决方案2】:

您的代码将不起作用。如果在null检查时它不是null,但是当你锁定时它是null怎么办?

There is the standing advise to always have a dedicated thing to lock ontoobject _mutex = new object(); 已成为习惯。

编辑:隐含私有,但您可能希望按照查尔斯的建议将其设为只读。

如果您锁定变量/字段,您可能会遇到此问题。或者对基元进行装箱(至尊不做任何事情,因为基元总是被放入不同的盒子)。或者其他人试图将其锁定在调用堆栈中,从而导致死锁。

示例代码:

//_mutex is private, readonly and exist exclusively for this operation
lock(_mutex){
  //You only do null checks after you got a lock
  if ( paramList != null ){
    foreach ( DictionaryEntry param in paramList ) {
      command.Parameters.AddWithValue(param.Key.ToString(), param.Value);
    }
  }
}

读取、写入或处理变量 paramList 的所有其他代码、它的任何 Dictionary 整体及其字段(包括任何副本*)也需要位于 lock(_mutex) 中。所以它们不能相互碰撞。

*原始类型和字符串的副本是例外。内置原始类型是不可变的。通常也不使用参考力学。并且 String 类也被设置为不可变的,以允许它以这种方式工作

【讨论】:

  • 我对你的建议感到困惑。你能根据我发布的代码给出示例代码吗?
  • 在尝试并运行后,我现在得到collection was modified
  • @HerrimanCoder:Foreach 不适用于集合,只能用于枚举数。转换是隐式完成的,但枚举器有一个规则:“如果您重新生成集合并且基础集合发生更改,则枚举器必须变为无效 - 并抛出该异常”。 docs.microsoft.com/en-us/dotnet/api/…
  • @HerrimanCoder:所以有 2 个选项:1)您没有向我们展示此代码执行的操作类似于从集合中删除元素 2)其他一些代码正在更改 paramList,而它位于该 foreach 循环中. Wich 清楚地表明,其他代码 not 在 lock 语句中。您是否忘记了一些从您放入 paramList 的实例中读取或删除的代码?
【解决方案3】:

这是解决我所有问题的解决方案:

var connection = getConnectionFromPool(sourceOrDC);

try {
   lock ( connection ) {
      using ( SqlCommand command = new SqlCommand(sql, connection) ) {
         command.CommandType = CommandType.Text;

         if ( paramList != null ) {
            foreach ( DictionaryEntry param in paramList ) {
               command.Parameters.AddWithValue(param.Key.ToString(), param.Value);
            }
         }

         command.CommandTimeout = 0;
         command.ExecuteNonQuery();
      }
   }

   return true;
}

我不得不在lock() 中包含更多代码,并锁定连接。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-07
    • 1970-01-01
    相关资源
    最近更新 更多