2011-02-12 42 views
5

我使用'添加Web引用'在vusual studio中为Web服务生成了代理类。生成的RTWebService类有一个方法SetValueAsync。我扩展了这个类,并添加了一个记录请求的SetValueRequest,并在发生错误时取消所有未决请求。每个请求我存储userState对象在我作为创建一个ArrayList如下:为什么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); 
} 

每当我提出请求并同时返回一个错误消息时,我在CancelPendingRequests中得到TargetInvocationException。内的例外是在CancelPendingRequests方法说法一个ArrayList一个InvalidOperationException

集合已修改;枚举操作可能不会执行。

所以它似乎SetValueRequest已修改requests对象,而我正在枚举它。我认为这是不可能的,因为我使用ArrayList的同步包装并使用SyncRoot同步枚举。我有点卡住这个,所以如果有人有一个想法?

回答

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,从而修改枚举内的请求。今天我遇到了一些事件的陷阱。

我通过枚举使用ToArray方法创建的requests对象的本地副本来解决此问题。

public void CancelPendingRequests() 
    if (requests.Count > 0) { 
    for (object request in requests.ToArray()) { 
     this.CancelAsync(request); 
    } 
    } 
} 
+1

你的代码还是坏了。如果您在迭代时更改列表,则for不会大声喊出,但是您仍然会遇到竞态条件错误。 – 2011-02-12 17:19:35

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(); 
     } 
    } 

}

+0

似乎不工作 – Jan 2011-02-12 15:18:35

+0

我想这可能已经涉及到我以前看过的访问权限修改闭合差。不知道为什么它不会工作。也许有人可以给出一个很好的解释。祝你好运。 – Xaisoft 2011-02-12 16:26:09

3
  1. 从来没有使用SyncRoot上它固有打破。 (如果你共享列表,你只是邀请一个死锁)

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

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

  4. 你可以使用的东西从System.Collection.Concurrent,或使用ReaderWriterLockSlim