RX订阅的意外行为 - subscribe()&等待任务完成:〜|

时间:2016-06-05 16:31:48

标签: c# async-await system.reactive

我有以下代码块,运行时我希望使用阻塞集合,并且在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我消耗阻塞集合!

思想?

2 个答案:

答案 0 :(得分:2)

您的Rx查询的主要问题是async/await部分,因为您没有处理或处理SynchronizationContext.Current

但他们在整个代码中遇到了其他问题。

SubscribeOnTaskPoolScheduleIDisposable课程中,你应该正确地实施和处理它。

此外,当您使用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);
                });

DelaySubscriptionDelay更有效,只是要注意,延迟不是同步的,所以最终可能会在另一个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进入图片,那么你可能还想确保你满足以上条件关注..

以下代码使用

示例代码

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());
}