简体   繁体   English

在 Reactive Extensions for .NET 中使用 Observable.FromEventPattern 时如何避免任何阻塞?

[英]How can I avoid any blocking when using Observable.FromEventPattern in Reactive Extensions for .NET?

I'm struggling with some concurrency issues in relation subscribing to an Observable.FromEventPattern() on the TaskPoolScheduler .我正在努力解决与订阅TaskPoolScheduler上的Observable.FromEventPattern()相关的一些并发问题。

Let me illustrate with a code example:让我用一个代码示例来说明:

var dataStore = new DataStore();

Observable.FromEventPattern<DataChangedEventArgs>(dataStore, nameof(dataStore.DataChanged))
    .SubscribeOn(TaskPoolScheduler.Default)
    .Select(x => x.EventArgs)
    .StartWith(new DataChangedEventArgs())
    .Throttle(TimeSpan.FromMilliseconds(25))
    .Select(x => 
    {
        Thread.Sleep(5000); // Simulate long-running calculation.
        var result = 42;
        return result;
    })
    .ObserveOn(new SynchronizationContextScheduler(SynchronizationContext.Current))
    .Subscribe(result =>
    {
        // Do some interesting work with the result.
        // ...

        // Do something that makes the DataStore raise another event.
        dataStore.RaiseDataChangedEvent(); // <- DEADLOCK!
    });

dataStore.RaiseDataChangedEvent(); // <- Returns immediately, i.e. does NOT wait for long-running calculation.
dataStore.RaiseDataChangedEvent(); // <- Blocks while waiting for the previous long-running calculation to complete, then returns eventually.

My issue is that, when any new items are emitted by the original observable Observable.FromEventPattern() (ie when the DataStore object raises new DataChanged events), then they appear to be blocked waiting for the previous items to finish flowing through the entire pipeline.我的问题是,当原始 observable Observable.FromEventPattern()发出任何新项目时(即当DataStore对象引发新的DataChanged事件时),它们似乎被阻塞,等待前一个项目完成流过整个管道.

Since the subscribing is done on the TaskPoolScheduler I had expected every new item emitted to simply spin up a new task, but actually, the source of the event instead seems to block on the event invocation if the pipeline is busy.由于订阅是在TaskPoolScheduler上完成的,我原TaskPoolScheduler发出的每个新项目都会简单地启动一个新任务,但实际上,如果管道繁忙,事件源似乎会阻塞事件调用。

How can I accomplish a subscription that executes every new emitted item (raised event) on it's own task/thread, such that the source object never blocks on its internal DataChangedEvent.Invoke() call?我怎样才能完成一个订阅,在它自己的任务/线程上执行每个新发出的项目(引发的事件),这样源对象永远不会阻塞它的内部DataChangedEvent.Invoke()调用?

(Except of course the Subscribe() lambda which should execute on the UI thread - which is already the case.) (当然,应该在 UI 线程上执行的Subscribe() lambda 除外 - 这已经是这种情况。)

As a side-note: @jonstodle mentioned in the #rxnet Slack channel that the TaskPoolScheduler might have different semantics than what I assumed.作为一个侧面说明:在#rxnet松弛通道@jonstodle提到TaskPoolScheduler可能有不同的语义比我承担。 Specifically he said it probably creates one task and does both the subscribing and the producing of values in an event loop inside of that one task.具体来说,他说它可能会创建一项任务,并在该任务内的事件循环中进行订阅和生成值。 But if that's the case, then I find it a bit strange that the first event invocation doesn't block (since the second one does).但如果是这样的话,那么我发现第一个事件调用没有阻塞(因为第二个事件调用)有点奇怪。 Seems to me that if the task pool task doing the subscription is asynchronous enough that the souce doesn't have to block on the first invocation, there shouldn't be a need to make it block on the second call either?在我看来,如果执行订阅的任务池任务足够异步以至于源不必在第一次调用时阻塞,那么也不需要在第二次调用时阻塞?

The issue you're hitting is simply the way Rx works - each value produced in a normal Rx pipeline is, well pipelined, and only one value is processed at a time.您遇到的问题只是 Rx 的工作方式 - 在正常 Rx 管道中生成的每个值都经过良好的管道处理,并且一次只处理一个值。 If the source of a Rx pipeline, in your case the FromEventPattern<DataChangedEventArgs> produces values faster than the observer(s) handles them then they get queued in the pipeline.如果 Rx 管道的来源,在您的情况下, FromEventPattern<DataChangedEventArgs>产生值的速度比观察者处理它们的速度快,那么它们会在管道中排队。

The rule is that each observer in the pipeline will only process one value at a time.规则是管道中的每个观察者一次只能处理一个值。 That happens for any scheduler, not just TaskPoolScheduler .任何调度程序都会发生这种情况,而不仅仅是TaskPoolScheduler

The way to make it work they way you want is quite simple - you create parallel pipelines and then merge the values back into a single pipeline.让它以您想要的方式工作的方法非常简单 - 您创建并行管道,然后将值合并回单个管道。

Here's the change:这是变化:

Observable
    .FromEventPattern<DataChangedEventArgs>(dataStore, nameof(dataStore.DataChanged))
    .SubscribeOn(TaskPoolScheduler.Default)
    .Select(x => x.EventArgs)
    .StartWith(new DataChangedEventArgs())
    .Throttle(TimeSpan.FromMilliseconds(25))
    .SelectMany(x =>
        Observable.Start(() =>
        {
            Thread.Sleep(5000); // Simulate long-running calculation.
            var result = 42;
            return result;
        }))
    .ObserveOn(new SynchronizationContextScheduler(SynchronizationContext.Current))
    .Subscribe(result =>
    {
        // Do some interesting work with the result.
        // ...

        // Do something that makes the DataStore raise another event.
        dataStore.RaiseDataChangedEvent();
    });

The .SelectMany(x => Observable.Start(() => replaces the .Select(x => allowing the values to being a new observable subscription which runs immediately and then it merges the values back into a single observable. .SelectMany(x => Observable.Start(() =>替换了.Select(x =>允许值成为一个新的可观察订阅,立即运行,然后将值合并回单个可观察对象。

You may prefer to write it as the semantically identical .Select(x => Observable.Start(() => ...)).Merge() .你可能更喜欢把它写成语义相同的.Select(x => Observable.Start(() => ...)).Merge()

Here's a simple example that shows how this works:这是一个简单的例子,展示了它是如何工作的:

var source = new Subject<int>();

source
    .SelectMany(x =>
        Observable.Start(() =>
        {
            Thread.Sleep(1000);
            return x * 2;
        }))
    .Subscribe(result =>
    {
        Console.WriteLine(result);
        source.OnNext(result);
        source.OnNext(result + 1);
    });

source.OnNext(1);

It produces:它产生:

2
4
6
14
12
8
10
24
28
30
26
16
20
22
18
48
50
56
52
58
60
62
54
32
34
46
44
40
42

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Observable.fromEventPattern TypedEventHandler - Observable.fromEventPattern TypedEventHandler ObservableCollection上的Observable.FromEventPattern - Observable.FromEventPattern on ObservableCollection 如何将使用Rx Observable.FromEventPattern编写的用于事件处理程序的函数转换为纯.net事件处理程序(不带Rx) - How to convert a function written using Rx Observable.FromEventPattern for event handlers to a pure .net event handler(without Rx) 何时使用Observable.FromEventPattern而不是Observable.FromEvent? - When to use Observable.FromEventPattern rather than Observable.FromEvent? 如何将SelectMany用于Observable.FromEventPattern序列上的异步逻辑? - How to use SelectMany for async logic on Observable.FromEventPattern sequence? 当使用TestScheduler将事件触发到具有ObserveOn的Observable.FromEventPattern时,在下一个事件被触发之前不会观察到事件 - When using TestScheduler to fire events into Observable.FromEventPattern that has an ObserveOn, the events aren't observed until next event is fired 用 Observable.Create 包裹 Observable.FromEventPattern - Wrap Observable.FromEventPattern with Observable.Create 为什么Observable.FromEventPattern接受调度程序? - Why Observable.FromEventPattern take in a scheduler? Observable.FromEventPattern(addHandler,removeHandler)-简化吗? - Observable.FromEventPattern(addHandler, removeHandler ) - simplification? 如何使用Observable FromEventPattern异步例程避免死锁? - How to avoid deadlock with Observable FromEventPattern Async routines?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM