我有以下代码块,运行时我希望使用阻塞集合,并且在Subscribe()操作中执行一些异步活动,这些异步活动在RX移动到观察者序列中的下一个条目之前等待。
实际发生的是所有条目都是并行处理(或看起来是)。
目前的代码如下!
uploadQueues[workspaceId] //BlockingCollection<FileEvent>>
.GetConsumingEnumerable()
.ToObservable()
.SubscribeOn(new TaskPoolScheduler(new TaskFactory()))
.Subscribe(
async fileEvent =>
{
//process file event
Debug.WriteLine($"Upload queue processor 1: {fileEvent.Event} | {fileEvent.SourcePath} => {fileEvent.DestPath}");
await Task.Delay(TimeSpan.FromSeconds(1));
})
任何指向正确方向的指针都会非常感激..也想知道是否只是生成一个长时间运行的TPL任务,而不是使用RX我消耗阻塞集合!
思想?
答案 0 :(得分:2)
您的Rx查询的主要问题是async/await
部分,因为您没有处理或处理SynchronizationContext.Current
但他们在整个代码中遇到了其他问题。
在SubscribeOn
,TaskPoolSchedule
是IDisposable
课程中,你应该正确地实施和处理它。
此外,当您使用ToObservable()
方法时,它不会使用任何IScheduler
- 请查看详细信息here。您可以指定EventLoopScheduler
以保证仅使用一个线程/资源进行处理
(虽然没有必要,因为GetConsumingEnumerable
已经锁定一个线程只是为了消费)。
如果您只想模拟延迟,最好的方法是:
enumerableItems.ToObservable()
.Select(a => Observable.Return(a).DelaySubscription(TimeSpan.FromSeconds(1)))
.Concat() // Keep push order
.Subscribe(
fileEvent =>
{
Debug.WriteLine(fileEvent);
});
DelaySubscription
比Delay
更有效,只是要注意,延迟不是同步的,所以最终可能会在另一个Thread.ThreadId
中结束,但这并不是&#因为顺序和顺序将被保留,这很重要。
现在,如果您想对Rx使用async / await并保持单一威胁,那么这是另一个问题的问题......
答案 1 :(得分:2)
[Upvoted @ J.Lennon的回答]
欢迎使用Rx。正如J.Lennon上面提到的,上面的代码建议有一些改进。
与同步或异步的任何代码一样,我们要考虑我们的资源以及我们如何确保我们之后进行清理。我们还需要考虑如何处理异常。除了这些问题,当使用异步(因此并发)时,我们还有额外的关注取消。我们应该提供取消操作的能力,我们也应该确保取消消费者所期望的。
虽然我认为Rx不是最适合阅读队列的工具(参见http://introtorx.com/Content/v1.0.10621.0/01_WhyRx.html#Wont),如果你真的想强迫Rx进入图片,那么你可能还想确保你满足以上条件关注..
以下代码使用
CancellationToken
s允许GetConsumingEnumerable()
取消https://msdn.microsoft.com/en-us/library/dd395014(v=vs.110).aspx)EventLoopScheduler
以确保单个线程专用于处理队列。这也使它序列化。 (http://introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html#EventLoopScheduler)Concat
提示,以确保您正在进行的异步工作也是序列化的。 (http://introtorx.com/Content/v1.0.10621.0/12_CombiningSequences.html#Concat)OnError
处理程序。然而,这让我回到了为什么我不喜欢Rx处理队列。你应该怎么做才能失败?尝试重新阅读消息并陷入无限循环,或将其丢弃在地板上并假装没有发生? (http://introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#Subscribe)示例代码
IDictionary<string, BlockingCollection<object>> uploadQueues = new Dictionary<string, BlockingCollection<Object>>();
public IObservable<object> ListenToQueueEvents(string workspaceId)
{
//Not sure what the return value is here, using `Object` as a place holder.
// Note that we are using an overload that takes a `CancellationToken`
return Observable.Create<object>((obs, ct) =>
{
var els = new EventLoopScheduler(ts => new Thread(ts)
{
IsBackground = true,//? or false? Should it stop the process from terminating if it still running?
Name = $"{workspaceId}Processor"
});
var subscription = uploadQueues[workspaceId] //BlockingCollection<FileEvent>>
.GetConsumingEnumerable(ct) //Allow cancellation while wating for next item
.ToObservable(els) //Serialise onto a single thread.
.Select(evt=>TheAsyncThingIWasDoingInTheSubscribe(evt).ToObservable())
.Concat()
.Subscribe(obs);
//You could try to dispose of the els (EventLoopScheduler), But I have had issues doing so in the past.
// Leaving as Background should allow it to die (but non deterministically) :-(
return Task.FromResult(subscription);
});
}
private static Task<object> TheAsyncThingIWasDoingInTheSubscribe(object evt)
{
//The return of the async thing you were doing in the subscribe
return Task.FromResult(new Object());
}