我在方法中有这样的代码:
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);
});
}
rx的编码方式是什么,以便在MarkAsHandledBy()
的多个并发调用中,IsHandledBy()
和HandleNext()
之间不会发生竞争?
编辑:
这是为了长时间轮询。为每个Web请求调用HandleNext()
。请求只能处理一条消息,然后返回给客户端。下一个请求接收下一条消息,依此类推。
完整的代码(当然还在进行中)是这样的:
public class Queue
{
readonly ISubject<MessageWrapper> messages;
public Queue() : this(TimeSpan.FromSeconds(30)) {}
public Queue(TimeSpan messageTimeout)
{
messages = new ReplaySubject<MessageWrapper>(messageTimeout);
}
public void Send(string channel, object message)
{
messages.OnNext(new MessageWrapper(new List<string> {channel}, message));
}
public void ReceiveNext(string clientId, string channel, Action<object> callback)
{
messages
.Where(message => message.Channels.Contains(channel) && !message.IsReceivedBy(clientId))
.Take(1)
.Subscribe(message =>
{
callback(message.Message);
message.MarkAsReceivedFor(clientId);
});
}
class MessageWrapper
{
readonly List<string> receivers;
public MessageWrapper(List<string> channels, object message)
{
receivers = new List<string>();
Channels = channels;
Message = message;
}
public List<string> Channels { get; private set; }
public object Message { get; private set; }
public void MarkAsReceivedFor(string clientId)
{
receivers.Add(clientId);
}
public bool IsReceivedBy(string clientId)
{
return receivers.Contains(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);
}
}
答案 0 :(得分:2)
在我看来,你正在为自己做一个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));
}
这是非常Rx-ish。这是一个很好的纯查询,您可以轻松取消订阅。
由于Action<object> callback
无疑与clientId
直接相关,因此我考虑采用逻辑来防止重复的消息处理。
现在你的代码非常程序化,不适合Rx。看起来你还没有完全了解如何最好地使用Rx。这是一个好的开始,但你需要在功能上更多地思考(如在函数式编程中)。
如果您必须按原样使用代码,我建议您进行一些更改。
在Queue
执行此操作:
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));
}
在MessageWrapper
摆脱MarkAsReceivedFor
&amp; IsReceivedBy
而是这样做:
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;
}
}
我真的不明白为什么你有.Take(1)
,但这些改变可能会减少竞争条件,具体取决于其原因。
答案 1 :(得分:1)
我不确定如此使用Rx是一种很好的做法。 Rx定义了流的概念,它要求不存在并发通知。
那就是说,为了回答你的问题,为避免出现竞争条件,请锁定IsReceivedBy
和MarkAsReceivedFor
方法。
至于更好的方法,您可以放弃整个处理业务,在收到请求时使用ConcurrentQueue和TryDequeue
消息(您只做Take(1)
- 哪个适合队列模型)。 Rx可以帮助您为每条消息提供一个TTL并将其从队列中删除,但您也可以在传入的请求中执行此操作。