我正在尝试用C#实现一个消费者。有许多发布者可以同时执行。我创建了三个示例,一个使用Rx和subject,一个使用BlockingCollection,第三个使用BlockingCollection中的ToObservable。在这个简单的例子中,它们都做同样的事情,我希望它们与多个生产者一起工作。
每种方法有哪些不同的特质?
我已经在使用Rx,所以我更喜欢这种方法。但我担心OnNext没有线程安全保证,我不知道排队语义是什么主题和默认调度程序。
是否有线程安全主题?
是否要处理所有邮件?
当这不起作用时还有其他任何情况吗?是同时处理?
void SubjectOnDefaultScheduler()
{
var observable = new Subject<long>();
observable.
ObserveOn(Scheduler.Default).
Subscribe(i => { DoWork(i); });
observable.OnNext(1);
observable.OnNext(2);
observable.OnNext(3);
}
不是Rx,但很容易适应使用/订阅它。它需要一个项目然后处理它。这应该是连续发生的。
void BlockingCollectionAndConsumingTask()
{
var blockingCollection = new BlockingCollection<long>();
var taskFactory = new TaskFactory();
taskFactory.StartNew(() =>
{
foreach (var i in blockingCollection.GetConsumingEnumerable())
{
DoWork(i);
}
});
blockingCollection.Add(1);
blockingCollection.Add(2);
blockingCollection.Add(3);
}
使用阻塞集合有点像主题似乎是一个很好的妥协。我猜是隐式地会安排到任务上,这样我就可以使用async / await,这是正确的吗?
void BlockingCollectionToObservable()
{
var blockingCollection = new BlockingCollection<long>();
blockingCollection.
GetConsumingEnumerable().
ToObservable(Scheduler.Default).
Subscribe(i => { DoWork(i); });
blockingCollection.Add(1);
blockingCollection.Add(2);
blockingCollection.Add(3);
}
答案 0 :(得分:6)
主题不是线程安全的。同时发布的OnNexts将同时直接调用Observer。我个人觉得这很令人惊讶,因为Rx的其他领域在多大程度上强制执行正确的语义。我只能假设这是出于性能考虑而做的。
主题是一种中途宿舍,因为它确实强制终止OnError或OnComplete - 在引发其中任何一个之后,OnNext是NOP。此行为 线程安全。
但是在Subject上使用Observable.Synchronize(),它将强制传出调用遵循正确的Rx语义。特别是,如果同时进行OnNext调用将会阻塞。
底层机制是标准的.NET锁。当锁被多个线程争用时,他们在先到先得的基础上被授予锁定大部分时间。在某些情况下,公平性受到侵犯。但是,您肯定会获得您正在寻找的序列化访问权限。
ObserveOn具有特定于平台的行为 - 如果可用,您可以提供SynchronizationContext
和OnNext调用已发布到它。使用调度程序,它最终将调用放到ConcurrentQueue<T>
并通过调度程序串行调度 - 因此执行的线程将取决于调度程序。无论哪种方式,排队行为也将强制执行正确的语义。
在这两种情况下(Synchronize&amp; ObserveOn),您当然不会丢失消息。使用ObserveOn,您可以通过选择Scheduler / Context隐式选择要处理消息的线程,使用Synchronize,您将在调用线程上处理消息。哪个更好取决于您的情况。
还有更多需要考虑的因素 - 例如,如果您的生产者超过您的消费者,您想要做什么。
你可能也想看看Rxx Consume:http://rxx.codeplex.com/SourceControl/changeset/view/63470#1100703
示例代码显示了Synchronize行为(Nuget Rx-Testing,Nunit) - 它与Thread.Sleep代码有点混淆,但它非常繁琐而且很糟糕而且我很懒::):
public class SubjectTests
{
[Test]
public void SubjectDoesNotRespectGrammar()
{
var subject = new Subject<int>();
var spy = new ObserverSpy(Scheduler.Default);
var sut = subject.Subscribe(spy);
// Swap the following with the preceding to make this test pass
//var sut = subject.Synchronize().Subscribe(spy);
Task.Factory.StartNew(() => subject.OnNext(1));
Task.Factory.StartNew(() => subject.OnNext(2));
Thread.Sleep(2000);
Assert.IsFalse(spy.ConcurrencyViolation);
}
private class ObserverSpy : IObserver<int>
{
private int _inOnNext;
public ObserverSpy(IScheduler scheduler)
{
_scheduler = scheduler;
}
public bool ConcurrencyViolation = false;
private readonly IScheduler _scheduler;
public void OnNext(int value)
{
var isInOnNext = Interlocked.CompareExchange(ref _inOnNext, 1, 0);
if (isInOnNext == 1)
{
ConcurrencyViolation = true;
return;
}
var wait = new ManualResetEvent(false);
_scheduler.Schedule(TimeSpan.FromSeconds(1), () => wait.Set());
wait.WaitOne();
_inOnNext = 0;
}
public void OnError(Exception error)
{
}
public void OnCompleted()
{
}
}
}