C# 如何在被动扩展中避免这种竞争条件

C# 如何在被动扩展中避免这种竞争条件,c#,system.reactive,C#,System.reactive,我在方法中有这样的代码: ISubject<Message> messages = new ReplaySubject<Message>(messageTimeout); public void HandleNext(string clientId, Action<object> callback) { messages.Where(message => !message.IsHandledBy(clientId)) .

我在方法中有这样的代码:

ISubject<Message> messages = new ReplaySubject<Message>(messageTimeout);

public void HandleNext(string clientId, Action<object> callback)
{
    messages.Where(message => !message.IsHandledBy(clientId))
            .Take(1)
            .Subscribe(message =>
                       {
                           callback(message.Message);
                           message.MarkAsHandledBy(clientId);
                       });
}
编辑2:

现在,我的代码如下所示:

public void ReceiveNext(string clientId, string channel, Action<object> callback)
{
    var subscription = Disposable.Empty;
    subscription = messages
        .Where(message => message.Channels.Contains(channel))
        .Subscribe(message =>
                   {
                       if (message.TryDispatchTo(clientId, callback))
                           subscription.Dispose();
                   });
}

class MessageWrapper
{
    readonly object message;
    readonly List<string> receivers;

    public MessageWrapper(List<string> channels, object message)
    {
        this.message = message;
        receivers = new List<string>();
        Channels = channels;
    }

    public List<string> Channels { get; private set; }

    public bool TryDispatchTo(string clientId, Action<object> handler)
    {
        lock (receivers)
        {
            if (IsReceivedBy(clientId)) return false;
            handler(message);
            MarkAsReceivedFor(clientId);
            return true;
        }
    }

    void MarkAsReceivedFor(string clientId)
    {
        receivers.Add(clientId);
    }

    bool IsReceivedBy(string clientId)
    {
        return receivers.Contains(clientId);
    }
}
public void ReceiveNext(字符串clientId、字符串通道、操作回调)
{
var subscription=Disposable.Empty;
订阅=消息
.Where(message=>message.Channels.Contains(channel))
.订阅(消息=>
{
if(message.TryDispatchTo(clientId,回调))
subscription.Dispose();
});
}
类消息包装器
{
只读对象消息;
只读列表接收者;
public MessageWrapper(列表通道、对象消息)
{
this.message=消息;
接收者=新列表();
通道=通道;
}
公共列表频道{get;private set;}
public bool TryDispatchTo(字符串clientId,操作处理程序)
{
锁(接收器)
{
if(IsReceivedBy(clientId))返回false;
处理程序(消息);
标记为已接收(客户ID);
返回true;
}
}
无效标记asreceivedFor(字符串clientId)
{
接收者。添加(客户端ID);
}
bool由(字符串clientId)接收
{
返回接收者。包含(clientId);
}
}

我不确定这样使用Rx是否是一种好的做法。Rx定义了不需要并发通知的流的概念

也就是说,为了回答您的问题,为了避免竞争条件,请在
接收的
标记内设置一个锁,用于
方法


至于更好的方法,您可以放弃整个处理业务,在接收请求时使用和
TryDequeue
消息(您只执行
Take(1)
,这符合队列模型)。Rx可以帮助您为每条消息提供一个TTL并将其从队列中删除,但您也可以在收到请求时这样做。

在我看来,您正在为自己制造Rx噩梦。Rx应该提供一种非常简单的方式将订阅者连接到您的消息

我喜欢这样一个事实,即您有一个自包含的类来保存您的
ReplaySubject
——它在代码中的其他地方停止恶意调用,并过早地调用
OnCompleted

但是,
ReceiveNext
方法不提供任何删除订阅者的方法。至少是内存泄漏。在
MessageWrapper
中跟踪客户端ID也是一种潜在的内存泄漏

我建议您尝试使用这种函数,而不是
ReceiveNext

public IDisposable RegisterChannel(string channel, Action<object> callback)
{
    return messages
        .Where(message => message.Channels.Contains(channel))
        .Subscribe(message => callback(message.Message));
}
MessageWrapper
中,去掉
MarkAsReceivedFor
&
是由
接收的,改为:

    public bool TryReceive(string clientId, Action<object> callback)
    {
        lock (receivers)
        {
            if (!receivers.Contains(clientId))
            {
                callback(this.Message);
                receivers.Add(clientId);
                return true;
            }
            else
                return false;
        }
    }
public bool TryReceive(字符串clientId,动作回调)
{
锁(接收器)
{
如果(!receivers.Contains(clientId))
{
回调(this.Message);
接收者。添加(客户端ID);
返回true;
}
其他的
返回false;
}
}

我真的不明白你为什么要使用
。尽管使用(1)
,但是这些更改可能会根据其原因减少比赛条件。

你为什么需要
HandleNext
?你不能用一个函数处理整个队列吗
HandleNext
对我来说似乎不太可靠(因为消息是可以这样变化的,而它不应该这样)。函数式编程通过不使用可变的东西来消除竞争。。。你说得对,我实际上是在轮询消息。我必须承认你让我在这里思考。我认为rx是这个的完美选择,现在我不确定了。你的代码似乎有点做作。这是您正在使用的实际代码,还是您在此处发布时“使其静音”?您能解释一下如何调用
HandleNext
?另外,正如旁注一样,Rx可能非常适合你所做的事情,但你这样做会扼杀它。好吧,我只是把它简化了一点:)它实际上工作得非常好。我只有一次比赛条件测试失败。根据你的评论,我现在更新了这个问题。好吧,我建议如下:处理代码只适用于整个队列。只要队列为空,代码就会等待队列中的下一条消息。因此,无需标记已处理的消息。语义将更像一个管道。您不需要显式地调用
HandleNext
:消息一到达队列,工作人员就会被消息推到队列中。非常感谢您的详细回答!至于你的最后一个建议,这正是我迄今为止所做的,但正如你所说的——它不是rx'y。。。它使用锁。现在我对内存泄漏的事情有些怀疑:我的印象是,执行.Take(1)会在恰好一个元素之后自动取消订阅(这就是它存在的原因)。这实际上是你自己的回答:,也许我误解了?至于clientId列表,我相信它们会定期被清除,因为ReplaySubject上有一个时间窗口?现在关于RegisterChannel方法,我不确定我是否得到了它。如果web请求调用RegisterChannel,然后收到消息,则请求将响应,然后关闭。现在,如果有另一条消息传入,则没有处理回调的活动请求,消息将丢失。RegisterChannel应该如何使用?它会不会将取消订阅的责任转移到回调函数中?@asgerhallas-使用
Take(1)
确实会在一个值后取消订阅,但您现在希望在收到每个值后立即调用
ReceiveNext
。你的代码不能做到这一点。尽管如此,由于代码的多线程特性,您可能会错过消息。你真的想创建一个你订阅一次的Rx查询
public IDisposable ReceiveNext(
    string clientId, string channel, Action<object> callback)
{
    return
        messages
            .Where(message => message.Channels.Contains(channel))
            .Take(1)
            .Subscribe(message =>
                message.TryReceive(clientId, callback));
}
    public bool TryReceive(string clientId, Action<object> callback)
    {
        lock (receivers)
        {
            if (!receivers.Contains(clientId))
            {
                callback(this.Message);
                receivers.Add(clientId);
                return true;
            }
            else
                return false;
        }
    }