Ensuring sequential disposing of multiple IDisposables

五迷三道 提交于 2019-12-11 03:47:30

问题


I have two IDisposables that I need to have disposed in sequential order. The ordering is important since the first IDisposable kills an Rx subscription that is relying on a service that will be killed by the second IDisposable. This is within a Windows Forms application where the subscription of the IObservable needs to happen on a different thread but the observing and disposing needs to happen on the UI thread. (Actually, I don't care if the disposing happens on the UI thread as long as the order is ensured.) So, in code I have roughly the following (once reduced):

SomeService = new DisposableService();
Subscription = Foo(someService).SubscribeOn(NewThreadScheduler.Default).ObserveOn(theForm).Subscribe(...)

On a number of UI events I need to dispose of both of these in order (Subscription and then SomeService). To do this I have tried using Rx's CompositeDisposable in addition to the ContextDisposable to provide serial disposing on the same thread:

_Disposable = new CompositeDisposable(new[] {                     
    new ContextDisposable(WindowsFormsSynchronizationContext.Current, Subscription),                     
    new ContextDisposable(WindowsFormsSynchronizationContext.Current, SomeService)});

The above does not work however. Based on my logging _Disposable and the ContextDisposable for SomeService are called on the same thread but the ContextDisposable is still occurring on a different thread concurent with the service being disposed (and thus resulting in race conditions and NPEs).

I've only been programming C# for a couple of weeks and so I'm sure the problem is my misunderstanding of how contexts and dispatchers work. What is the correct approach to this problem?


回答1:


Unless I'm misunderstanding something, you can control which thread disposes what. Who subscribes on which thread doesn't matter. Look at this example

internal class Program
{
    private static void Main(string[] args)
    {
        ReactiveTest rx1 = null;
        ReactiveTest rx2 = null;

        var thread1 = new Thread(() => rx1 = new ReactiveTest());
        var thread2 = new Thread(() => rx2 = new ReactiveTest());

        thread1.Start();
        thread2.Start();

        Thread.Sleep(TimeSpan.FromSeconds(1));

        thread1.Join();
        thread2.Join();

        rx1.Dispose();
        rx2.Dispose();
    }
}

public class ReactiveTest : IDisposable
{
    private IDisposable _timerObservable;

    private object _lock = new object();

    public ReactiveTest()
    {
        _timerObservable = Observable.Interval(TimeSpan.FromMilliseconds(250)).Subscribe(i => 
            Console.WriteLine("[{0}] - {1}", Thread.CurrentThread.ManagedThreadId, i));
    }

    public void Dispose()
    {
        lock (_lock)
        {
            _timerObservable.Dispose();
            Console.WriteLine("[{0}] - DISPOSING", Thread.CurrentThread.ManagedThreadId);
        }
    }
}

This outputs

[14] - 0
[7] - 0
[15] - 1
[7] - 1
[14] - 2
[15] - 2
[10] - DISPOSING
[10] - DISPOSING

You can see we subscribed on two seperate threads, then disposed on a third. I only locked the dispose in case you had something threadsafe that needs to happen in the subscription. In this example it's unnecessary really.




回答2:


SubscribeOn schedules the calls to both Subscribe and Dispose. Therefore, calling Dispose on your Subscription variable, regardless of whether execution is currently on the UI thread or not, results in the subscription being scheduled for disposal by NewThreadScheduler.Default.

It's almost never a good idea to use SubscribeOn; however, in your case you're claiming that it solves 50% of your problem - and that's 50% more than most uses I've seen - so I must question whether or not you actually need the subscription to execute on a background thread in the first place. It's awfully expensive to create a brand new thread and then invoke a method on it, compared to just calling a method on the UI thread directly, if all the method does is begin some asynchronous work such as sending a network request or reading a file. Perhaps if computing a network message to be sent is proven to be overly time-consuming, then using SubscribeOn may be correct; although, of course, only if you want disposal scheduled as well.

If the subscription to your observable must execute on a background thread, yet disposal must remain free-threaded, then consider using the following operator instead (untested).

public static class ObservableExtensions
{
  public static IObservable<TSource> SubscribeOn<TSource>(
    this IObservable<TSource> source,
    bool doNotScheduleDisposal, 
    IScheduler scheduler)
  {
    if (!doNotScheduleDisposal)
    {
      return source.SubscribeOn(scheduler);
    }

    return Observable.Create<TSource>(observer =>
      {
        // Implementation is based on that of the native SubscribeOn operator in Rx
        var s = new SingleAssignmentDisposable();
        var d = new SerialDisposable();
        d.Disposable = s;
        s.Disposable = scheduler.Schedule(() =>
        {
          d.Disposable = source.SubscribeSafe(observer);
        });
        return d;
      });
  }
}


来源:https://stackoverflow.com/questions/14126971/ensuring-sequential-disposing-of-multiple-idisposables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!