[英]Can't unsubscribe from Rx
背景
我正在寫一些執行以下操作的軟件:
第一次單擊“開始”時一切正常,但此后沒有。 第一次單擊“開始”時,將得到如下輸出:
看起來不錯,這里沒有錯。 但是,當我第二次單擊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
接口使您可以取消訂閱尚未自然終止的可觀察訂閱。 如果您的觀察對象發送OnCompleted
或OnError
通知,則將自動為您處理訂閱。
這種方法的一個明顯優勢是,您可以創建一個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.myTest
和this.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.