简体   繁体   English

无法退订Rx

[英]Can't unsubscribe from Rx

Background 背景

I am writing some software that does the following: 我正在写一些执行以下操作的软件:

  1. The user clicks on "start". 用户单击“开始”。
  2. A task is started that performs some work and launches events to update the GUI. 启动一个任务,该任务执行一些工作并启动事件以更新GUI。
  3. An observable consumes the events from the task, and prints data to a rich-text-box in the GUI. 一个可观察对象消耗任务中的事件,并将数据打印到GUI中的富文本框。

Everything works alright when I click on "START" the first time, but not after that. 第一次单击“开始”时一切正常,但此后没有。 The first time I click on start, I get an output that looks like: 第一次单击“开始”时,将得到如下输出:

单击一次START

This looks alright, nothing wrong here. 看起来不错,这里没有错。 However, when I click on START the second time I get the following output. 但是,当我第二次单击START时,将得到以下输出。

再次单击开始

Now, I believe I know why this might be happening. 现在,我相信我知道为什么会这样。 From what I can tell, my observer never unsubscribed from the first time I clicked on START and thus everything gets printed twice. 据我所知,我的观察者从第一次单击“开始”开始就从未取消订阅,因此所有内容都会打印两次。 When clicking on the start button, here is what happens: 单击开始按钮时,将发生以下情况:

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

I am new to reactive extensions in .NET (have been using it for less than one hour). 我对.NET中的反应式扩展是陌生的(已经使用了不到一个小时)。 I am essentially using one of Stephen Cleary's examples from the Concurrency in C# cookbook in order to get started here. 为了从这里开始,我基本上是使用C#并发中的Stephen Cleary的示例之一。 Despite all of this, I believe I sort of know what the problem is... 尽管如此,我相信我有点知道问题出在哪里...

Proposed Plan 拟议计划

If I could just unsubscribe from the observable, then I think that this problem would go away. 如果我可以取消订阅可观察的项目,那么我认为这个问题将消失。 I think it's a little more complicated that that though, based on the reactive patterns used at ReactiveX.io it sounds like I should actually be smartly observing the task only for the during in which it actually exists then unsubscribing naturally. 我认为这有点复杂,但是基于ReactiveX.io上使用的反应模式,听起来我实际上应该只在任务实际存在的时期内明智地观察该任务,然后自然退订。 This is as far as I have come with debugging this problem, and I am not 100% sure that this unsubscribing thing would actually fix the problem. 就调试此问题而言,这是我所能做到的,并且我不是100%确信此取消订阅的内容实际上可以解决该问题。

Question

Is there some way to unsubscribe from the observable? 有什么方法可以退订可观察的吗? Or, is there a way that I could observe my task only for the duration that it exists ? 或者,是否有一种方法可以让我仅在任务存在的时间内对其进行观察?

Rx uses the IDisposable interface to enable you to unsubscribe from an observable subscription that hasn't yet naturally ended. Rx使用IDisposable接口使您可以取消订阅尚未自然终止的可观察订阅。 If your observable sends a OnCompleted or OnError notification then the subscription is automatically disposed for you. 如果您的观察对象发送OnCompletedOnError通知,则将自动为您处理订阅。

One clear advantage to this approach is that you can create a single CompositeDisposable to aggregate all of your subscriptions to enable a single unsubscribe. 这种方法的一个明显优势是,您可以创建一个CompositeDisposable来汇总所有订阅,以实现单个退订。 This is far better than hodge-podge of detaches required for removing event handlers. 这远胜于删除事件处理程序所需的分离大杂烩。

In your code you're not ending the subscription so for each click on button_go you're creating a new subscription. 在您的代码中,您不会结束订阅,因此,每次单击button_go都将创建一个新订阅。

There are four solutions you can use, each somewhat different. 您可以使用四种解决方案,每种解决方案都略有不同。

(1) (1)

Remember, the key to needing to call .Dispose() is if your observable subsription hasn't naturally ended and you want it to end. 请记住,需要调用.Dispose()的关键是,如果您的可观察到的.Dispose()没有自然地结束并且您希望它结束​​。 So, you can simply add a .Take(1) to your query to make it naturally end after one value is produced. 因此,您只需在查询中添加.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();
}

The subscription is then automatically disposed for you. 订阅将自动为您处理。

(2) (2)

You could use SerialDisposable to manage each subscription. 您可以使用SerialDisposable管理每个订阅。 The MSDN documentation describes it as: MSDN文档将其描述为:

Represents a disposable whose underlying disposable can be swapped for another disposable which causes the previous underlying disposable to be disposed. 代表一个一次性用品,其基础一次性用品可以交换为另一种一次性用品,从而导致之前的基础一次性用品被处置。

Your code then would look like this: 您的代码将如下所示:

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) (3)

You could always make sure that you only ever create the subscription once. 您始终可以确保只创建一次订阅。

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) (4)

The final approach is where the entire operation is wrapped up in an observable and is instigated for each new subscription. 最终的方法是将整个操作包装在一个可观察的对象中,并针对每个新订阅进行指示。 This is the correct way to work with Rx. 这是使用Rx的正确方法。 Observables should hold their own state so that subscriptions can be 100% independent of each other. 可观察对象应保持自己的状态,以便订阅可以100%彼此独立。

Now, your code uses this.myTest & this.runningTest so it clearly has state. 现在,您的代码使用this.myTestthis.runningTest因此它显然具有状态。 You should try to remove these. 您应该尝试删除这些。 But without doing so your code would look like this: 但是如果不这样做,您的代码将如下所示:

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

Ideally you should incorporate the creation and destruction of myTest inside the observable. 理想情况下,您应该将myTest的创建和销毁myTest到可观察对象中。

So, I would be inclined to do something like this: 所以,我倾向于做这样的事情:

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

This last one is really the answer to your question: "Or, is there a way that I could observe my task only for the duration that it exists?" 最后一个确实是您的问题的答案:“或者,是否有一种方法可以让我仅在任务存在的时间内观察任务?”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM