Bu hatanın kaynağına inemiyorum, çünkü hata ayıklayıcı eklendiğinde hata oluşmuyor gibi görünüyor. Kod aşağıda verilmiştir.
Bu, bir Windows hizmetindeki bir WCF sunucusudur. NotifySubscribers yöntemi, bir veri olayı olduğunda (rastgele aralıklarla, ancak çok sık değil - günde yaklaşık 800 kez) hizmet tarafından çağrılır.
Bir Windows Forms istemcisi abone olduğunda, abone kimliği aboneler sözlüğüne eklenir ve istemci abonelikten çıktığında sözlükten silinir. Hata, bir istemci abonelikten çıktığında (veya çıktıktan sonra) meydana gelir. NotifySubscribers() yöntemi bir sonraki çağrılışında foreach() döngüsünün konu satırındaki hata ile başarısız olduğu görülmektedir. Yöntem, hatayı aşağıdaki kodda gösterildiği gibi uygulama günlüğüne yazar. Bir hata ayıklayıcı eklendiğinde ve bir istemci abonelikten çıktığında, kod düzgün şekilde yürütülür.
Bu kodda bir sorun görüyor musunuz? Sözlüğü iş parçacığı güvenli hale getirmem gerekiyor mu?
[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);
}
}
}
Muhtemelen gerçekleşen şey, SignalData'nın döngü sırasında dolaylı olarak aboneler sözlüğünü değiştirmesi ve bu mesaja yol açmasıdır. Bunu değiştirerek doğrulayabilirsiniz
foreach(Subscriber s in subscribers.Values)
için
foreach(Subscriber s in subscribers.Values.ToList())
Eğer haklıysam, sorun ortadan kalkacak.
subscribers.Values.ToList() çağrısı, foreach'in başlangıcında subscribers.Values değerlerini ayrı bir listeye kopyalar. Başka hiçbir şeyin bu listeye erişimi yoktur (bir değişken adı bile yoktur!), bu nedenle döngü içinde hiçbir şey onu değiştiremez.
Bir abone aboneliğini iptal ettiğinde, numaralandırma sırasında Aboneler koleksiyonunun içeriğini değiştirirsiniz.
Bunu düzeltmenin birkaç yolu vardır; bunlardan biri for döngüsünü açık bir .ToList()
kullanacak şekilde değiştirmektir:
public void NotifySubscribers(DataRecord sr)
{
foreach(Subscriber s in subscribers.Values.ToList())
{
^^^^^^^^^
...
Bence daha etkili bir yol, "kaldırılacak" olan her şeyi içine koyduğunuzu beyan ettiğiniz başka bir listeye sahip olmaktır. Ana döngünüzü bitirdikten sonra (.ToList() olmadan), "to be removed" listesi üzerinde başka bir döngü yaparsınız ve her girişi olduğu gibi kaldırırsınız. Yani sınıfınıza şunu eklersiniz:
private List<Guid> toBeRemoved = new List<Guid>();
Sonra değiştirirsin:
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 );
}
Bu sadece sorununuzu çözmekle kalmayacak, sözlüğünüzden bir liste oluşturmaya devam etmek zorunda kalmanızı da önleyecektir, ki bu da çok sayıda abone varsa pahalıdır. Herhangi bir yinelemede kaldırılacak abonelerin listesinin listedeki toplam sayıdan daha düşük olduğunu varsayarsak, bu daha hızlı olmalıdır. Ancak elbette, özel kullanım durumunuzda herhangi bir şüphe varsa, durumun böyle olduğundan emin olmak için profil oluşturmaktan çekinmeyin.