确保顺序处理多个IDisposable

时间:2013-01-02 18:08:32

标签: c# system.reactive

我需要按顺序排列两个IDisposables。排序很重要,因为第一个IDisposable会杀死依赖于将被第二个IDisposable杀死的服务的Rx订阅。这是在Windows窗体应用程序中,IObservable的订阅需要在不同的线程上进行,但观察和处理需要在UI线程上进行。 (实际上,只要确保顺序,我不关心是否在UI线程上进行处理。)因此,在代码中我大致有以下内容(一旦减少):

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

在许多UI事件中,我需要按顺序处理这两个事件(Subscription,然后是SomeService)。为此,除了CompositeDisposable之外,我还尝试使用Rx的ContextDisposable来在同一个线程上提供串行处理:

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

然而,上述方法无效。基于我的日志记录_DisposableContextDisposable SomeService在同一个线程上调用,但ContextDisposable仍然发生在不同的线程上,并且服务正在被处理(和从而导致竞争条件和NPEs)。

我只用了几个星期编写C#,所以我确定问题是我对上下文和调度程序的工作方式的误解。解决这个问题的正确方法是什么?

2 个答案:

答案 0 :(得分:0)

除非我误解了什么,否则你可以控制哪个线程处理什么。谁订阅哪个线程无关紧要。看看这个例子

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

此输出

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

你可以看到我们订阅了两个单独的线程,然后处理了第三个。我只锁定了dispose,以防你有一些需要在订阅中发生的线程安全。在这个例子中,它实际上是不必要的。

答案 1 :(得分:0)

SubscribeOn会安排对SubscribeDispose的调用。因此,在订阅变量上调用Dispose,无论当前是否在UI线程上执行,都会导致订阅被NewThreadScheduler.Default计划处理。

使用SubscribeOn几乎不是一个好主意;但是,在你的情况下,你声称它解决了50%的问题 - 这比我见过的大多数用途多50% - 所以我必须质疑你是否真的需要在后台线程上执行订阅第一名。创建一个全新的线程,然后在其上调用一个方法相比,直接在UI线程上直接调用一个方法,如果所有方法都是开始一些异步工作,如发送网络请求或读取文件,这是非常昂贵的。也许如果计算要发送的网络消息过于耗时,那么使用SubscribeOn可能是正确的;当然,只有当你想要安排处理时。

如果您的observable的订阅必须在后台线程上执行,但处理必须保持自由线程,那么请考虑使用以下运算符(未经测试)。

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