簡體   English   中英

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

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

我正在努力解決與訂閱TaskPoolScheduler上的Observable.FromEventPattern()相關的一些並發問題。

讓我用一個代碼示例來說明:

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.

我的問題是,當原始 observable Observable.FromEventPattern()發出任何新項目時(即當DataStore對象引發新的DataChanged事件時),它們似乎被阻塞,等待前一個項目完成流過整個管道.

由於訂閱是在TaskPoolScheduler上完成的,我原TaskPoolScheduler發出的每個新項目都會簡單地啟動一個新任務,但實際上,如果管道繁忙,事件源似乎會阻塞事件調用。

我怎樣才能完成一個訂閱,在它自己的任務/線程上執行每個新發出的項目(引發的事件),這樣源對象永遠不會阻塞它的內部DataChangedEvent.Invoke()調用?

(當然,應該在 UI 線程上執行的Subscribe() lambda 除外 - 這已經是這種情況。)

作為一個側面說明:在#rxnet松弛通道@jonstodle提到TaskPoolScheduler可能有不同的語義比我承擔。 具體來說,他說它可能會創建一項任務,並在該任務內的事件循環中進行訂閱和生成值。 但如果是這樣的話,那么我發現第一個事件調用沒有阻塞(因為第二個事件調用)有點奇怪。 在我看來,如果執行訂閱的任務池任務足夠異步以至於源不必在第一次調用時阻塞,那么也不需要在第二次調用時阻塞?

您遇到的問題只是 Rx 的工作方式 - 在正常 Rx 管道中生成的每個值都經過良好的管道處理,並且一次只處理一個值。 如果 Rx 管道的來源,在您的情況下, FromEventPattern<DataChangedEventArgs>產生值的速度比觀察者處理它們的速度快,那么它們會在管道中排隊。

規則是管道中的每個觀察者一次只能處理一個值。 任何調度程序都會發生這種情況,而不僅僅是TaskPoolScheduler

讓它以您想要的方式工作的方法非常簡單 - 您創建並行管道,然后將值合並回單個管道。

這是變化:

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

.SelectMany(x => Observable.Start(() =>替換了.Select(x =>允許值成為一個新的可觀察訂閱,立即運行,然后將值合並回單個可觀察對象。

你可能更喜歡把它寫成語義相同的.Select(x => Observable.Start(() => ...)).Merge()

這是一個簡單的例子,展示了它是如何工作的:

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

它產生:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM