簡體   English   中英

無法退訂Rx

[英]Can't unsubscribe from Rx

背景

我正在寫一些執行以下操作的軟件:

  1. 用戶單擊“開始”。
  2. 啟動一個任務,該任務執行一些工作並啟動事件以更新GUI。
  3. 一個可觀察對象消耗任務中的事件,並將數據打印到GUI中的富文本框。

第一次單擊“開始”時一切正常,但此后沒有。 第一次單擊“開始”時,將得到如下輸出:

單擊一次START

看起來不錯,這里沒有錯。 但是,當我第二次單擊START時,將得到以下輸出。

再次單擊開始

現在,我相信我知道為什么會這樣。 據我所知,我的觀察者從第一次單擊“開始”開始就從未取消訂閱,因此所有內容都會打印兩次。 單擊開始按鈕時,將發生以下情況:

    /// <summary>
    /// Starts the test.
    /// </summary>
    /// <param name="sender">The "start" button.</param>
    /// <param name="e">Clicking on the "start" button.</param>
    private void button_go_Click(object sender, RoutedEventArgs e)
    {
        var uiContext = SynchronizationContext.Current;
        var results = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
            handler => (s, a) => handler(s, a),
            handler => this.myTest.Results += handler,
            handler => this.myTest.Results -= handler)
            .ObserveOn(uiContext)
            .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

        // start running the test
        this.runningTest = new Task(() => { this.myTest.Run(); });
        this.runningTest.Start();

        // block on purpose, wait for the task to finish
        this.runningTest.Wait();
    }

我對.NET中的反應式擴展是陌生的(已經使用了不到一個小時)。 為了從這里開始,我基本上是使用C#並發中的Stephen Cleary的示例之一。 盡管如此,我相信我有點知道問題出在哪里...

擬議計划

如果我可以取消訂閱可觀察的項目,那么我認為這個問題將消失。 我認為這有點復雜,但是基於ReactiveX.io上使用的反應模式,聽起來我實際上應該只在任務實際存在的時期內明智地觀察該任務,然后自然退訂。 就調試此問題而言,這是我所能做到的,並且我不是100%確信此取消訂閱的內容實際上可以解決該問題。

有什么方法可以退訂可觀察的嗎? 或者,是否有一種方法可以讓我僅在任務存在的時間內對其進行觀察?

Rx使用IDisposable接口使您可以取消訂閱尚未自然終止的可觀察訂閱。 如果您的觀察對象發送OnCompletedOnError通知,則將自動為您處理訂閱。

這種方法的一個明顯優勢是,您可以創建一個CompositeDisposable來匯總所有訂閱,以實現單個退訂。 這遠勝於刪除事件處理程序所需的分離大雜燴。

在您的代碼中,您不會結束訂閱,因此,每次單擊button_go都將創建一個新訂閱。

您可以使用四種解決方案,每種解決方案都略有不同。

(1)

請記住,需要調用.Dispose()的關鍵是,如果您的可觀察到的.Dispose()沒有自然地結束並且您希望它結束​​。 因此,您只需在查詢中添加.Take(1)即可使其自然地在產生一個值后結束。

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    var results = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
        handler => (s, a) => handler(s, a),
        handler => this.myTest.Results += handler,
        handler => this.myTest.Results -= handler)
        .Take(1)
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

    // start running the test
    this.runningTest = new Task(() => { this.myTest.Run(); });
    this.runningTest.Start();

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

訂閱將自動為您處理。

(2)

您可以使用SerialDisposable管理每個訂閱。 MSDN文檔將其描述為:

代表一個一次性用品,其基礎一次性用品可以交換為另一種一次性用品,從而導致之前的基礎一次性用品被處置。

您的代碼將如下所示:

private SerialDisposable _results = new SerialDisposable();

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    _results.Disposable = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
        handler => (s, a) => handler(s, a),
        handler => this.myTest.Results += handler,
        handler => this.myTest.Results -= handler)
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

    // start running the test
    this.runningTest = new Task(() => { this.myTest.Run(); });
    this.runningTest.Start();

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

(3)

您始終可以確保只創建一次訂閱。

private IDisposable _results = null;

private void button_go_Click(object sender, RoutedEventArgs e)
{
    if (_results == null)
    {
        var uiContext = SynchronizationContext.Current;
        _results = Observable.FromEventPattern<TestResultHandler, TestResultArgs>(
            handler => (s, a) => handler(s, a),
            handler => this.myTest.Results += handler,
            handler => this.myTest.Results -= handler)
            .ObserveOn(uiContext)
            .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));
    }

    // start running the test
    this.runningTest = new Task(() => { this.myTest.Run(); });
    this.runningTest.Start();

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

(4)

最終的方法是將整個操作包裝在一個可觀察的對象中,並針對每個新訂閱進行指示。 這是使用Rx的正確方法。 可觀察對象應保持自己的狀態,以便訂閱可以100%彼此獨立。

現在,您的代碼使用this.myTestthis.runningTest因此它顯然具有狀態。 您應該嘗試刪除這些。 但是如果不這樣做,您的代碼將如下所示:

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    Observable
        .Create<System.Reactive.EventPattern<TestResultArgs>>(o =>
        {
            var subscription =
                Observable
                    .FromEventPattern<TestResultHandler, TestResultArgs>(
                        h => this.myTest.Results += h,
                        h => this.myTest.Results -= h)
                    .Take(1)
                    .Subscribe(o);
            this.runningTest = new Task(() => { this.myTest.Run(); });
            this.runningTest.Start();
            return subscription;
        })
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));

    // block on purpose, wait for the task to finish
    this.runningTest.Wait();
}

理想情況下,您應該將myTest的創建和銷毀myTest到可觀察對象中。

所以,我傾向於做這樣的事情:

private void button_go_Click(object sender, RoutedEventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    Observable
        .Create<System.Reactive.EventPattern<TestResultArgs>>(o =>
        {
            var myTest = new MyTest();
            var subscription =
                Observable
                    .FromEventPattern<TestResultHandler, TestResultArgs>(
                        h => myTest.Results += h,
                        h => myTest.Results -= h)
                    .Take(1)
                    .Subscribe(o);
            myTest.Run();
            return subscription;
        })
        .ObserveOn(uiContext)
        .Subscribe(x => this.richTextBox_testResults.Document.Blocks.Add(new Paragraph(new Run(x.EventArgs.Results))));
}

最后一個確實是您的問題的答案:“或者,是否有一種方法可以讓我僅在任務存在的時間內觀察任務?”

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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