为什么ArrayList的同步包装器不起作用?

时间:2011-02-12 14:51:56

标签: c# multithreading web-services arraylist

我使用“添加Web引用”在Visual Studio中为Web服务生成了代理类。生成的RTWebService类有一个方法SetValueAsync。我扩展了这个类并添加了一个SetValueRequest来跟踪请求并在发生错误时取消所有待处理的请求。对于每个请求,我将userState对象存储在我创建的ArrayList中,如下所示:

requests = ArrayList.Synchronized(new ArrayList());

我创建了一个方法:

public void CancelPendingRequests() {
  lock (requests.SyncRoot) {
    if (requests.Count > 0) {
      foreach (object request in requests) {
        this.CancelAsync(request);
      }
      requests.Clear();
    }
  }
}

我在SetValueCompleted事件上返回请求时调用此方法:

private void onRequestComplete(
    object sender, 
    Service.SetValueCompletedEventArgs args
) {
  lock (syncResponse) {
    if (args.Cancelled) {
      return;
    }

    if (args.UserState != null) {
      requests.Remove(args.UserState);
    }

    if (args.Error != null) {
      CancelPendingRequests();
    }
  }
}

要发起新请求,请致电:

public void SetValueRequest(string tag, string value) {
  var request = new object();
  this.SetValueAsync(tag, value, request);
  requests.Add(request);
}

每当我发出请求并同时返回错误时,我会在TargetInvocationException中获得CancelPendingRequests。内部异常是InvalidOperationException方法中ArrayList上的CancelPendingRequests

  

收藏被修改;枚举操作可能无法执行。

所以看起来SetValueRequest在我枚举它时修改了requests对象。我认为这是不可能的,因为我使用ArrayList的同步包装器并使用SyncRoot来同步枚举。我有点坚持这个,所以如果有人有想法吗?

3 个答案:

答案 0 :(得分:3)

  1. 永远不会使用SyncRoot,它本身就是破碎的。 (如果你共享列表,你只是邀请死锁)

  2. 不要使用ArrayList,它应该标记为“Deprecated”。

  3. ArrayList.Synchronized返回的东西工作得更慢但线程安全,即在一组操作期间它不是线程安全的。

  4. 您可以使用System.Collection.Concurrent中的内容,也可以使用ReaderWriterLockSlim

答案 1 :(得分:2)

原始回答

我通过删除枚举解决了这个问题。我现在用:

public void CancelPendingRequests() {
  lock (requests.SyncRoot) {
    if (requests.Count > 0) {
      for (int i = 0; i < requests.Count; i++) {
        this.CancelAsync(requests[i]);
      }
      requests.Clear();
    }
  }
}

这似乎可以解决问题。我仍然有点担心这个lock (requests.SyncRoot)在枚举上不起作用,为什么它会在这里起作用?无论如何,我现在无法像以前那样重现异常,所以我认为这个问题已经解决了。我不能再浪费时间了。

修改

忘掉我上面的愚蠢回答。我正在研究一个项目,需要取得进展。我现在追查了这个问题:

所以看来这个bug根本不是多线程相关的。所有代码都在同一个线程上执行,我不需要那些锁。问题在于我在枚举中取消了请求。 CancelAsync方法会引发SetValueCompleted事件,而requests.Remove事件又会调用requests,从而修改枚举中的请求。我今天了解了一些事件的陷阱。

我通过枚举使用ToArray方法创建的public void CancelPendingRequests() if (requests.Count > 0) { for (object request in requests.ToArray()) { this.CancelAsync(request); } } } 对象的本地副本来解决问题。

{{1}}

答案 2 :(得分:0)

尝试为每个请求对象的CancelPendingRequests方法添加一个局部变量,如下所示:

  public void CancelPendingRequests() {
  lock (requests.SyncRoot) 
  {
      if (requests.Count > 0) 
      {
          foreach (object request in requests) 
          {
          object currentRequest = request; //Add this
          this.CancelAsync(currentRequest);
          }
          requests.Clear();
       }
  }

}