Search code examples
c#.netsystem.reactivedisposeobjectdisposedexception

EventLoopScheduler: unexpected behavior on Dispose ObjectDisposedException


When calling Dispose on an EventLoopScheduler (that has at least one item in its working queue) it will throw an ObjectDisposedException. The exception is thrown from its worker thread.

I've seen and read the two questions that already exist:

However, I think some of the answers are not quite correct, Quoting Intro to Rx regarding EventLoopScheduler:

The EventLoopScheduler implements IDisposable, and calling Dispose will allow the thread to terminate. As with any implementation of IDisposable, it is appropriate that you explicitly manage the lifetime of the resources you create.

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

They provide an example about how to use EventLoopScheduler correctly:

Observable
    .Using(()=>new EventLoopScheduler(), els=> GetPrices(els))
    .Subscribe(...)

Unfortunately this example does not work (at least not for me :-). Given this piece of code:

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

I would expect see a text message after 2 seconds without any error (even though the subscription has been disposed after 1 second). The EventLoopScheduler cannot cancel an ongoing operation, that's okay for me.

What you actually get, is the message and an unhandled ObjectDisposedException.

So, is this a bug or am I doing it wrong? :-)

To workaround this exception, I currently wrap the EventLoopScheduler and call scheduler.Schedule(() => scheduler.Dispose()) on wrapper.Dispose().


Solution

  • My comments above with James' answer. This "answer" is here to provide the sample code that "fixes" the issue.

    I do however believe that there is a bug with the EventLoopScheduler. I don't think* it should continue to schedule work recursively if it has been disposed.

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

    *but I completely reserve the right to change my mind when I am convinced otherwise.

    FWIW : Generally I only use an ELS as a readonly field in a service that I want to dedicate a thread to processing some inbound work. e.g. I only want to use one thread to read from the network or disk for that service. In this case I create an ELS and it will do any work. It then is disposed when the class containing it is disposed. I don't think I would use it very often at all as the sample from IntroToRx.com shows.