我使用“添加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来同步枚举。我有点坚持这个,所以如果有人有想法吗?
答案 0 :(得分:3)
永远不会使用SyncRoot,它本身就是破碎的。 (如果你共享列表,你只是邀请死锁)
不要使用ArrayList,它应该标记为“Deprecated”。
ArrayList.Synchronized返回的东西工作得更慢但不线程安全,即在一组操作期间它不是线程安全的。
您可以使用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();
}
}
}