2011-02-12 34 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