繁体   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