1. ホーム
  2. スクリプト・コラム
  3. その他

[解決済み】コレクションが変更され、列挙操作が実行されないことがある。

2022-01-10 04:49:11

質問

コードは次のとおりです。

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

実行すると、エラーが発生します。

Collection was modified; enumeration operation may not execute

しかし、デバッガを付けてもエラーは出ませんし、問題の所在が分かりません。

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

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

解決方法は?

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

foreach(Subscriber s in subscribers.Values)

への

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

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

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