2011-02-12 14 views
5

He generado las clases proxy para un servicio web en vusual studio con 'Agregar referencia web'. La clase RTWebService generada tiene un método SetValueAsync. Amplié esta clase y agregué un SetValueRequest que realiza un seguimiento de las solicitudes y cancela todas las solicitudes pendientes cuando ocurre un error. Con cada petición que almacenar el objeto userState en un ArrayList creé la siguiente manera:¿Por qué no funciona el contenedor sincronizado para ArrayList?

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

creé un método:

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

que llamo este método cuando una solicitud de beneficios dentro del evento 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(); 
    } 
    } 
} 

Para iniciar una nueva solicitud que llamo:

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

Cada vez que realizo una solicitud y, al mismo tiempo, una respuesta regresa con un error, obtengo un TargetInvocationException en el CancelPendingRequests. La excepción interna es un InvalidOperationException en un ArrayList en el CancelPendingRequests método dicho:

Collection se modificó; la operación de enumeración no se puede ejecutar.

por lo que parece SetValueRequest ha modificado el objeto requests mientras yo estaba enumerando ella. Pensé que esto era imposible porque utilicé el contenedor sincronizado para ArrayList y usé SyncRoot para sincronizar la enumeración. Estoy un poco atrapado en esto así que si alguien tiene una idea?

Respuesta

2

respuesta original

trabajé en torno al problema mediante la eliminación de la enumeración. Ahora uso:

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

Esto parece hacer el truco. Todavía estoy un poco preocupado de que este lock (requests.SyncRoot) no funcionó en la enumeración, ¿por qué iba a funcionar aquí? De todos modos, ahora no puedo reproducir la excepción como pude antes, así que considero que este problema está resuelto. No puedo perder más tiempo en esto.

EDITAR

olvido mi respuesta tonta anteriormente. Estaba trabajando en un proyecto y necesitaba avanzar. Seguí el problema ahora:

Parece que este error no estaba relacionado con subprocesos múltiples. Todo el código se ejecutó en el mismo hilo, no necesitaba esos bloqueos. El problema radica en el hecho de que estaba cancelando las solicitudes en mi enumeración. El método CancelAsync plantea el evento SetValueCompleted que a su vez llama al requests.Remove, modificando así las solicitudes dentro de la enumeración. Aprendí algunas trampas con los eventos de hoy.

Resolví el problema enumerando una copia local del objeto requests que creé con el método ToArray.

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

tu código aún está roto. the for no gritará fuerte si cambia la lista mientras la itera, pero aún tendrá errores de condiciones de carrera. –

+0

De hecho, eso es lo que quise decir, la pregunta es ** ¿por qué? ¿Por qué se cambia la lista a pesar de que la sincronizo con 'requests.SyncRoot'. No eliminaré elementos para que este no se rompa. – Jan

+0

1. la excepción lanzada claramente significa que estás equivocado, la lista está siendo manipulada mientras iteras. 2. ArrayList.Synchronized solo sincroniza acciones individuales, no transacciones lógicas. obtener el enumerador de la lista está sincronizado, pero una vez que toca la lista, todos los enumeradores anteriores se rompen. hay una manera de arreglar esto dentro de ArrayList, puedes hacerlo a través de SyncRoot, pero no deberías, porque es un mecanismo roto –

0

Tratando de añadir una variable local a su método CancelPendingRequests para cada objeto de la petición de esta manera:

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

no parece trabajar – Jan

+0

pensé que podría haber estado relacionado con el error de cierre de acceso Modificado he visto antes . No sé por qué no funcionaría sin embargo. Tal vez alguien pueda dar una buena explicación. Buena suerte. – Xaisoft

3
  1. nunca utilice SyncRoot está inherentemente roto. (si comparte la lista solo invita a un interbloqueo)

  2. No utilice ArrayList, debe estar marcado como "Deprecated".

  3. algo de retorno ArrayList.Synchronized que funciona más lentamente, pero es no hilo de seguridad, es decir, que no es hilo de seguridad durante una serie de operaciones.

  4. usted puede usar algo de System.Collection.Concurrent, o utilizar ReaderWriterLockSlim

+0

Estoy desarrollando para .NET 2.0 para este proyecto, creo que System.Collection.Concurrent o ReaderWriterLockSlim son 4.0? Lo resolví de manera diferente: copio las solicitudes ArrayList con 'ToArray()'. – Jan

+0

@ Jan- 1. System.Collection.Concurrent solo está en .Net 4, o en Reactive Extensions. 2. ReaderWriterLockSlim está en .Net 3.5 embargo. 3. ToArray() tampoco es seguro para hilos ... –

+0

¿Por qué msdn indica que todas las operaciones (excepto la enumeración) en ArrayList son seguras para hilos si se llaman a través del contenedor sincronizado? Acabo de eliminar efectivamente la enumeración en ArrayList y lo hice en una copia de matriz. – Jan

Cuestiones relacionadas