1. ホーム
  2. c#

[解決済み] コレクションが変更されたため、列挙操作が実行されない可能性がある

2022-01-28 19:05:26

質問

デバッガを付けるとこのエラーは発生しないようなので、真相が分かりません。

コレクションが変更されました。列挙操作が実行されない可能性があります。

以下はそのコードです。

WindowsサービスにおけるWCFサーバです。メソッド NotifySubscribers() は、データイベントがあるたびにサービスから呼び出されます(ランダムな間隔ですが、それほど頻繁ではなく、1日に約800回です)。

Windows Formsクライアントが購読すると、購読者IDが購読者辞書に追加され、クライアントが購読を解除すると、辞書から削除されます。このエラーは、クライアントが購読を中止したとき(または中止した後)に発生します。どうやら、次回以降に NotifySubscribers() メソッドが呼び出されると foreach() ループが失敗し、件名にエラーが表示されます。このメソッドは、以下のコードに示すように、アプリケーションログにエラーを書き込みます。デバッガが接続され、クライアントが配信を停止した場合、コードは正常に実行されます。

このコードに問題はないでしょうか?辞書をスレッドセーフにする必要があるのでしょうか?

[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);
        }
    }
}

解決方法は?

何が起こっているかというと SignalData は、ループの間にフードの下で間接的に購読者辞書を変更し、そのメッセージにつながるのです。 これを確認するには

foreach(Subscriber s in subscribers.Values)

への

foreach(Subscriber s in subscribers.Values.ToList())

私が正しければ、問題は解消される。

呼称 subscribers.Values.ToList() の値をコピーします。 subscribers.Values の先頭にある別のリストに追加します。 foreach . このリストには他の誰もアクセスできないので(変数名さえない!)、ループの中では何も変更できない。