收藏已修改; 枚举操作可能不会执行

我无法得到这个错误的底部,因为当调试器被连接时,它似乎不会发生。 以下是代码。

这是Windows服务中的WCF服务器。 每当发生数据事件时(服务器会随机调用NotifySubscribers方法,但并不经常 - 每天大约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())

如果我是对的,问题就会消失


当订阅者取消订阅时,您将在枚举期间更改订阅者集合的内容。

有几种方法可以解决这个问题,一种方法是将for循环改为使用显式的.ToList()

public void NotifySubscribers(DataRecord sr)  
{
    foreach(Subscriber s in subscribers.Values.ToList())
    {
                                              ^^^^^^^^^  
        ...

在我看来,更有效的方法是制定另一个列表,声明您将任何“要删除”的内容放入。 然后,在完成主循环(没有.ToList())之后,对“要被删除”列表执行另一个循环,在每个条目发生时删除它们。 所以在你的课堂中你增加了:

private List<Guid> toBeRemoved = new List<Guid>();

然后您将其更改为:

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

这不仅能解决你的问题,还能防止你不得不从你的字典中创建一个列表,如果那里有很多用户,这个列表很贵。 假设在任何给定迭代中要删除的用户列表都低于列表中的总数,这应该会更快。 但当然,如果您对特定使用情况有任何疑问,可以自由地对其进行描述以确保是这种情况。

链接地址: http://www.djcxy.com/p/51423.html

上一篇: Collection was modified; enumeration operation may not execute

下一篇: Can a C# anonymous class implement an interface?