EventLoopScheduler:Dispose ObjectDisposedException上的意外行为

时间:2016-04-01 19:42:02

标签: c# .net system.reactive dispose objectdisposedexception

EventLoopScheduler(在其工作队列中至少有一个项目)上调用ObjectDisposedException时,它会抛出EventLoopScheduler。从其工作线程抛出异常。

我已经看过并阅读了已经存在的两个问题:

但是,我认为一些答案并不完全正确,引用关于EventLoopScheduler的介绍Rx

  

EventLoopScheduler实现了IDisposable,调用Dispose将允许线程终止。与IDisposable的任何实现一样,您明确管理您创建的资源的生命周期是恰当的。

来源:http://introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html#EventLoopScheduler

他们提供了有关如何正确使用Observable .Using(()=>new EventLoopScheduler(), els=> GetPrices(els)) .Subscribe(...) 的示例:

internal class Program
{
    private static void Main(string[] args)
    {
        var source = new Subject<string>();
        var subscription = Observable.Using(
            () => new EventLoopScheduler(),
            scheduler => source
                .ObserveOn(scheduler)
                .Do(LongRunningAction))
            .Subscribe();

        source.OnNext("First action (2 seconds)");
        Thread.Sleep(TimeSpan.FromSeconds(1));
        subscription.Dispose(); // Scheduler is still busy!
        Console.ReadLine(); 
    }

    private static void LongRunningAction(string text) {
        Thread.Sleep(TimeSpan.FromSeconds(2));
        Console.WriteLine(text);
    }
}

不幸的是这个例子不起作用(至少不适合我:-)。鉴于这段代码:

EventLoopScheduler

我希望在2秒后看到一条短信而没有任何错误(即使订阅已在1秒后处理完毕)。 ObjectDisposedException无法取消正在进行的操作,这对我没用。

您实际得到的是消息和未处理的EventLoopScheduler

那么,这是一个错误还是我做错了? : - )

要解决此异常,我现在打包scheduler.Schedule(() => scheduler.Dispose())并在wrapper.Dispose()上致电{{1}}。

3 个答案:

答案 0 :(得分:1)

我在上面的评论与詹姆斯的回答。这个“答案”在这里提供“修复”问题的示例代码。

但我确实认为EventLoopScheduler存在错误。我不认为*它应该继续以递归的方式安排工作,如果它被处理掉了。

void Main()
{
    //In my example GetPrices is the source. 
    //  I meant that you could use an ELS to do some heavy work to get prices.
    //var source = new Subject<string>();   
    var subscription = Observable.Using(
        () => new EventLoopScheduler(),
        scheduler =>
        {
            return Observable.Create<string>((obs, ct) =>
            {
                var scheduleItem = scheduler.Schedule(0, (state,self) => {
                    //Do work to get price (network request? or Heavy CPU work?)
                    var price = state.ToString("c");
                    LongRunningAction(price);
                    obs.OnNext(price);
                    //Without this check, we see that the Scheduler will try to 
                    //  recursively call itself even when disposed.
                    if(!ct.IsCancellationRequested)
                        self(state+1);
                });
                return Task.FromResult(scheduleItem);
            });
        })
        .Subscribe();

    Thread.Sleep(TimeSpan.FromSeconds(1));
    subscription.Dispose(); // Scheduler is still busy!
    Console.ReadLine();
}

private static void LongRunningAction(string text)
{
    Thread.Sleep(TimeSpan.FromSeconds(2));
    Console.WriteLine(text);
}

*但是当我确信时,我完全保留改变主意的权利。

FWIW:通常我只在一个服务中使用ELS作为readonly字段,我想将一个线程用于处理一些入站工作。例如我只想使用一个线程从网络或磁盘读取该服务。在这种情况下,我创建了一个ELS,它将完成任何工作。然后在放置包含它的类时将其丢弃。我不认为我会经常使用它,因为来自IntroToRx.com的样本显示。

答案 1 :(得分:0)

所以,在被脱离背景引用之后,我被迫回应。 :)让我们将引用扩展到重要位:

  

你没有处理EventLoopScheduler的业务! 一旦你有了   将其传递给其他Rx运营商,您已经承担了责任   对于它。

问题是,您正试图让观察者(订阅者)清理调度程序。但观察者将调度程序传递给 observable 。如果你想要处理调度程序,你必须考虑它是现在“拥有”它的那个可观察对象。观察者知道:

  • 订阅时
  • 取消订阅时
  • 当它向所有订阅者发送OnComplete / OnError时

通过这些信息,可以很好地了解何时可以处理任何给定的调度程序。 (即便如此,如果你正在尝试进行通用清理,它需要在终结器中处理调度程序,因为这是唯一可以保证其他用户在没有特殊知识的情况下不会出现的情况。)< / p>

然而,个人订户无法保证拥有任何此类信息 - 潜在的其他订户的知识,以及何时发送最后的事件都不会暴露给它。可观察它通过调度程序,可以用各种时髦的方式聚集在它上面:调用疯狂的方法,这些方法可以睡得很多;因为它幻想它而在空气中制造事件;将事件推迟到下周二;回应取消订阅事件,在冰箱上张贴一张纸条并承诺到达mañana,诚实。

那么,您是否希望每次都安全地清理该调度程序?然后你需要让你的观察能够做到。

内置运营商不会为此烦恼 - 我怀疑这不是一个大问题,因为在大多数用例中它并不是必需的。事实上,我认为我从未见过需要处置EventLoopScheduler的情况 - 它们一直被用于程序的生命周期。很容易被认为你需要处理你看到的每个IDisposable - 但实际上使用Rx,它通常是没有必要的(特别是对于订阅,Dispose它实际上只是取消订阅的请求 - 而不是命令清理资源。当IDisposable做出非常好的订阅处理时,Rx团队不想创建另一个接口。)

EventLoopScheduler在不忙时暂停它的线程 - 所以大多数时候你不需要担心清理,除非你创建了一些任意数量(提示:你真的不应该'我需要这样做。)

如果你这样做,你可能想要查看NewThreadScheduler是否会改为实际使用EventLoopScheduler,而是以特殊的秘密(即内部)模式退出线程。调度程序队列为空 - 但重新使用。是的,尽管流行的误解恰恰相反,NewThreadScheduler 会重复使用线程,因此在单个订阅者的负载下没有大的关联线程创建成本。只有当多个用户在播放时才会出现多个线程,或者当它进入空闲时,下一个事件将导致线程创建。

但是如果你使用EventLoopScheduler,你可能在一个地方使用它来将事物绑定到一个全局共享的事件循环(毕竟 - 这就是事件循环通常做的事情 - 将事件集中到一个线程中一个应用程序) - 所以很少需要清理该线程,因为无论如何它都将在该过程中消失。

答案 2 :(得分:0)

好的,我有一些有用的东西。但它不是线程安全的,相关的代码行标有注释。我猜我应该打开一张错误票: - /

private static void Main(string[] args)
{
    var originSource = new Subject<string>();
    var subscription = UsingEventLoop(originSource)
        .Do(LongRunningAction) // runs on EventLoopScheduler thread
        .Subscribe();

    originSource.OnNext("First action (appears after 2 seconds)");
    originSource.OnNext("Second action (must not appear");

    Thread.Sleep(TimeSpan.FromSeconds(1));
    subscription.Dispose(); // Scheduler is still busy with first action!

    Console.WriteLine("Press any key to exit.");
    Console.ReadLine();
}

private static IObservable<TValue> UsingEventLoop<TValue>(IObservable<TValue> source)
{
    return Observable.Using(
        () => new EventLoopScheduler(),
        scheduler => Observable.Create<TValue>((obs, ct) =>
        {
            return Task.FromResult(source.Subscribe(value =>
            {
                // The following check+call is NOT thread safe!
                if (!ct.IsCancellationRequested) 
                {
                    scheduler.Schedule(() => obs.OnNext(value));
                }
            }));
        }));
}

private static void LongRunningAction<TValue>(TValue value) {
    Thread.Sleep(TimeSpan.FromSeconds(2));
    Console.WriteLine(value);
}