No puedo llegar al fondo de este error, porque cuando el depurador está conectado, no parece ocurrir. A continuación se muestra el código.
Este es un servidor WCF en un servicio de Windows. El método NotifySubscribers es llamado por el servicio cada vez que hay un evento de datos (a intervalos aleatorios, pero no muy a menudo - alrededor de 800 veces por día).
Cuando un cliente de Windows Forms se suscribe, el ID del suscriptor se añade al diccionario de suscriptores, y cuando el cliente se da de baja, se borra del diccionario. El error se produce cuando (o después) un cliente se da de baja. Parece que la siguiente vez que se llama al método NotifySubscribers(), el bucle foreach() falla con el error en la línea de asunto. El método escribe el error en el registro de la aplicación como se muestra en el código siguiente. Cuando se adjunta un depurador y un cliente se da de baja, el código se ejecuta bien.
¿Ves algún problema en este código? ¿Necesito hacer que el diccionario sea seguro para los hilos?
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class SubscriptionServer : ISubscriptionServer
{
private static IDictionary<Guid, Subscriber> subscribers;
public SubscriptionServer()
{
subscribers = new Dictionary<Guid, Subscriber>();
}
public void NotifySubscribers(DataRecord sr)
{
foreach(Subscriber s in subscribers.Values)
{
try
{
s.Callback.SignalData(sr);
}
catch (Exception e)
{
DCS.WriteToApplicationLog(e.Message,
System.Diagnostics.EventLogEntryType.Error);
UnsubscribeEvent(s.ClientId);
}
}
}
public Guid SubscribeEvent(string clientDescription)
{
Subscriber subscriber = new Subscriber();
subscriber.Callback = OperationContext.Current.
GetCallbackChannel<IDCSCallback>();
subscribers.Add(subscriber.ClientId, subscriber);
return subscriber.ClientId;
}
public void UnsubscribeEvent(Guid clientId)
{
try
{
subscribers.Remove(clientId);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("Unsubscribe Error " +
e.Message);
}
}
}
Lo que probablemente está sucediendo es que SignalData está cambiando indirectamente el diccionario de suscriptores bajo el capó durante el bucle y conduce a ese mensaje. Puede verificar esto cambiando
foreach(Subscriber s in subscribers.Values)
A
foreach(Subscriber s in subscribers.Values.ToList())
Si tengo razón, el problema desaparecerá
Al llamar a subscribers.Values.ToList() se copian los valores de subscribers.Values a una lista separada al inicio del foreach. Nada más tiene acceso a esta lista (¡ni siquiera tiene un nombre de variable!), así que nada puede modificarla dentro del bucle.
Cuando un abonado se da de baja, se está cambiando el contenido de la colección de Abonados durante la enumeración.
Hay varias formas de solucionar esto, una de ellas es cambiar el bucle for para utilizar un .ToList()
explícito:
public void NotifySubscribers(DataRecord sr)
{
foreach(Subscriber s in subscribers.Values.ToList())
{
^^^^^^^^^
...
Una manera más eficiente, en mi opinión, es tener otra lista que usted declara que usted pone todo lo que es "para ser eliminado" en. Entonces, después de terminar tu bucle principal (sin el .ToList()), haces otro bucle sobre la lista "a eliminar", eliminando cada entrada a medida que sucede. Así que en tu clase añades:
private List<Guid> toBeRemoved = new List<Guid>();
Luego lo cambias por
public void NotifySubscribers(DataRecord sr)
{
toBeRemoved.Clear();
...your unchanged code skipped...
foreach ( Guid clientId in toBeRemoved )
{
try
{
subscribers.Remove(clientId);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("Unsubscribe Error " +
e.Message);
}
}
}
...your unchanged code skipped...
public void UnsubscribeEvent(Guid clientId)
{
toBeRemoved.Add( clientId );
}
Esto no sólo resolverá tu problema, sino que evitará que tengas que seguir creando una lista desde tu diccionario, lo cual es costoso si hay muchos suscriptores ahí. Asumiendo que la lista de suscriptores a eliminar en cualquier iteración es menor que el número total de la lista, esto debería ser más rápido. Pero, por supuesto, siéntase libre de hacer un perfil para asegurarse de que ese es el caso si hay alguna duda en su situación de uso específica.