[英]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.