C# 从ConcurrentDictionary调用异步回调-潜在死锁?

C# 从ConcurrentDictionary调用异步回调-潜在死锁?,c#,concurrency,async-await,C#,Concurrency,Async Await,我有一个并发字典,用于管理订阅(并与其他代码链接),为了本例的目的,显示了一个简化版本 当添加或删除主题时,我需要更新外部消息总线,而不是添加额外的同步机制,并围绕字典添加和消息主题添加订阅包装一个阻塞部分,我正在使用并发字典,并在成功添加项目时回调一个操作: class SubscriptionManager { readonly ConcurrentDictionary<string, SubscribedTypes> subscriptions = new Concurre

我有一个并发字典,用于管理订阅(并与其他代码链接),为了本例的目的,显示了一个简化版本

当添加或删除主题时,我需要更新外部消息总线,而不是添加额外的同步机制,并围绕字典添加和消息主题添加订阅包装一个阻塞部分,我正在使用并发字典,并在成功添加项目时回调一个操作:

class SubscriptionManager
{
  readonly ConcurrentDictionary<string, SubscribedTypes> subscriptions = new ConcurrentDictionary<string, SubscribedTypes>();

   public void AddDynamic(string topic, Type type, Action<string> dynamicCallback)
   {
      subscriptions
          .AddOrUpdate(
                    topic,
                    (topic) =>
                    {
                        var subscribedTypes = new SubscribedTypes(qos);
                        /* do some work and checks */

                        dynamicCallback(topic); // call back the calling code

                        return subscribedTypes;
                    },
                    (topic, subscribedTypes) =>
                    {
                        /* snip */
                        subscribedTypes.Add(type);
                        return subscribedTypes;
                    });
   }
}
原始问题的更新

匿名lambda是否作为异步函数调用调用回答-这不是一个好主意。可以配置消息总线,因此可以通过IoC生命周期管理singleton或在消息总线内实现SubscriptionManager实例

在这里,性能是至关重要的,因为在review上,一些应用程序将动态创建大量订阅,并且词典将被持续读取

因此,我设置了ReaderWriterLockSlim锁,以最大限度地减少读取时的锁定(写入时升级为完全锁定),并且当需要订阅消息总线时,它在关键部分之外完成

public class SubscriptionManager
{
    // SubscribedTypes is a dictionary with some additional features
    private readonly Dictionary<string, SubscribedTypes> dict = new Dictionary<string, SubscribedTypes>();
    private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private readonly IMessageBus messageBus;
    
    SubscriptionManager(IMessageBus messageBus)
    {
        this.messageBus = messageBus;
    }

    public async Task AddDynamicAsync(string topic, Type type)
    {
        var subscriptionRequired = false;
        
        WriteLock(() => {
            if(this.dict.TryGetValue(topic, var out SubscribedTypes value))
            {
                // just add - no need to subscribe, is already subscribed when created for first time
                value.DynamicRegistrations.Add(handlerType);                
            }
            else
            {
                // this topic is new, add it
                var subscribedTypes = new SubscribedTypes(qos);
                subscribedTypes.DynamicRegistrations.Add(handlerType);
                this.dict[topic] = subscribedTypes;

                // we will need to register this on external bus
                subscriptionRequired = true;
            }           
        });
        
        if(subscriptionRequired)
            await this.messageBus.Subscribe(topic);
    }
    
    public async Task RemoveDynamicAsync(string topic, Type type)
    {
        var unsubscribeRequired = false;
    
        WriteLock(() => {
            if(this.dict.TryGetValue(topic, var out SubscribedTypes value))
            {
                value.DynamicRegistrations.Remove(handlerType);             
                unsubscribeRequired = true;
            }
            else
            {
                throw new InvalidOperationException($"topic '{topic}' not registered.");
            }       
        });
        
        if(unsubscribeRequired)
            await this.messageBus.Unsubscribe(topic);
    }
    
    public IEnumerable<Type> GetTypesForTopic(string topic)
    {
        var found = ReadLock(() => {
            if(this.dict.TryGetValue(topic, var out SubscribedTypes value))
                return value.GetAll();  
            else
                return new List<Type>();
        });
    }

    private SubscribedTypes ReadLock(Func<SubscribedTypes> func)
    {
        rwLock.EnterReadLock();
        try { return func(); }
        finally { rwLock.ExitReadLock(); }
    }

    private void WriteLock(Action action)
    {
        rwLock.EnterWriteLock();
        try { action(); }
        finally { rwLock.ExitWriteLock(); }
    }               
}
公共类订阅管理器
{
//SubscribedTypes是一个具有一些附加功能的字典
专用只读词典dict=新词典();
private ReaderWriterLockSlim rBlock=new ReaderWriterLockSlim();
专用只读IMessageBus messageBus;
订阅管理器(IMessageBus messageBus)
{
this.messageBus=messageBus;
}
公共异步任务AddDynamicAsync(字符串主题,类型)
{
var subscriptionRequired=false;
写回(()=>{
if(this.dict.TryGetValue(主题,var out SubscribedTypes值))
{
//只需添加-无需订阅,第一次创建时已订阅
value.DynamicRegistrations.Add(handlerType);
}
其他的
{
//此主题是新的,请添加它
var subscribedTypes=新的subscribedTypes(qos);
subscribedTypes.DynamicRegistrations.Add(handlerType);
this.dict[topic]=订阅类型;
//我们需要在外部总线上注册
subscriptionRequired=true;
}           
});
如果(需要订阅)
等待这个.messageBus.Subscribe(主题);
}
公共异步任务RemoveDynamicAsync(字符串主题,类型)
{
var unsubscribeRequired=假;
写回(()=>{
if(this.dict.TryGetValue(主题,var out SubscribedTypes值))
{
value.DynamicRegistrations.Remove(handlerType);
unsubscribeRequired=true;
}
其他的
{
抛出新的InvalidOperationException($“未注册主题“{topic}”);
}       
});
如果(需要取消订阅)
等待此消息。messageBus。取消订阅(主题);
}
公共IEnumerable GetTypesForTopic(字符串主题)
{
var found=ReadLock(()=>{
if(this.dict.TryGetValue(主题,var out SubscribedTypes值))
返回值:GetAll();
其他的
返回新列表();
});
}
私有SubscribedTypes读锁(Func Func)
{
rwLock.EnterReadLock();
尝试{return func();}
最后{rwLock.exitradlock();}
}
私有无效写回(操作)
{
rBlock.EnterWriteLock();
试试{action();}
最后{rwLock.ExitWriteLock();}
}               
}

您使用
ConcurrentDictionary的第一种方法是有问题的,因为根据:

如果在不同的线程上同时调用
AddOrUpdate
addValueFactory
可能会被多次调用,但它的键/值对可能不会为每次调用添加到字典中

[…]在锁外部调用
addValueFactory
updateValueFactory
委托,以避免在锁下执行未知代码时可能出现的问题。因此,
AddOrUpdate
对于
ConcurrentDictionary
类上的所有其他操作不是原子的

您使用
ConcurrentDictionary
的方式表明,您希望对
AddOrUpdate
的每次调用最多执行一次提供的lambda,但这并不保证


使用普通
字典
+
的第二种方法将无法编译,因为。您必须移动
wait messageBus.Subscribe(主题)
在受保护区域之外,或者使用异步节流器,如。

使用
ConcurrentDictionary
的第一种方法是有问题的,因为根据:

如果在不同的线程上同时调用
AddOrUpdate
addValueFactory
可能会被多次调用,但它的键/值对可能不会为每次调用添加到字典中

[…]在锁外部调用
addValueFactory
updateValueFactory
委托,以避免在锁下执行未知代码时可能出现的问题。因此,
AddOrUpdate
对于
ConcurrentDictionary
类上的所有其他操作不是原子的

您使用
ConcurrentDictionary
的方式表明,您希望对
AddOrUpdate
的每次调用最多执行一次提供的lambda,但这并不保证

使用普通
字典
+
的第二种方法将无法编译,因为
public class SubscriptionManager
{
    // SubscribedTypes is a dictionary with some additional features
    private readonly Dictionary<string, SubscribedTypes> dict = new Dictionary<string, SubscribedTypes>();
    private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private readonly IMessageBus messageBus;
    
    SubscriptionManager(IMessageBus messageBus)
    {
        this.messageBus = messageBus;
    }

    public async Task AddDynamicAsync(string topic, Type type)
    {
        var subscriptionRequired = false;
        
        WriteLock(() => {
            if(this.dict.TryGetValue(topic, var out SubscribedTypes value))
            {
                // just add - no need to subscribe, is already subscribed when created for first time
                value.DynamicRegistrations.Add(handlerType);                
            }
            else
            {
                // this topic is new, add it
                var subscribedTypes = new SubscribedTypes(qos);
                subscribedTypes.DynamicRegistrations.Add(handlerType);
                this.dict[topic] = subscribedTypes;

                // we will need to register this on external bus
                subscriptionRequired = true;
            }           
        });
        
        if(subscriptionRequired)
            await this.messageBus.Subscribe(topic);
    }
    
    public async Task RemoveDynamicAsync(string topic, Type type)
    {
        var unsubscribeRequired = false;
    
        WriteLock(() => {
            if(this.dict.TryGetValue(topic, var out SubscribedTypes value))
            {
                value.DynamicRegistrations.Remove(handlerType);             
                unsubscribeRequired = true;
            }
            else
            {
                throw new InvalidOperationException($"topic '{topic}' not registered.");
            }       
        });
        
        if(unsubscribeRequired)
            await this.messageBus.Unsubscribe(topic);
    }
    
    public IEnumerable<Type> GetTypesForTopic(string topic)
    {
        var found = ReadLock(() => {
            if(this.dict.TryGetValue(topic, var out SubscribedTypes value))
                return value.GetAll();  
            else
                return new List<Type>();
        });
    }

    private SubscribedTypes ReadLock(Func<SubscribedTypes> func)
    {
        rwLock.EnterReadLock();
        try { return func(); }
        finally { rwLock.ExitReadLock(); }
    }

    private void WriteLock(Action action)
    {
        rwLock.EnterWriteLock();
        try { action(); }
        finally { rwLock.ExitWriteLock(); }
    }               
}