Controlling what thread an Rx subscription is Disposed on after SubscribedOn has been used

*爱你&永不变心* 提交于 2019-12-11 03:37:49

问题


I have a Rx subscription that I SubscribeOn with a different thread to prevent it from blocking. However, I want the disposal of that subscription to block due to resource management issues. I have not been able to figure out how to accomplish this within the context of either a console app or a winforms app (I have both use cases). Below is working code of a reduced case that simulates what I am doing:

internal class Program
{

    private static void Log(string msg)
    {
        Console.WriteLine("[{0}] " + msg, Thread.CurrentThread.ManagedThreadId.ToString());
    }

    private static void Main(string[] args)
    {

        var foo = Observable.Create<long>(obs =>
            {
                Log("Subscribing starting.. this will take a few seconds..");
                Thread.Sleep(TimeSpan.FromSeconds(2));
                var sub =
                    Observable.Interval(TimeSpan.FromSeconds(1))
                              .Do(_ => Log("I am polling..."))
                              .Subscribe(obs);
                return Disposable.Create(() =>
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(3));
                        sub.Dispose();
                        Log("Disposing is really done now!");
                    });
            });

        Log("I am subscribing..");
        var disp = foo.SubscribeOn(NewThreadScheduler.Default).Subscribe(i => Log("Processing " + i.ToString()));
        Log("I have returned from subscribing...");

        // SC.Current is null in a ConsoleApp :/  Can I get a SC that uses my current thread?
        //var dispSynced = new ContextDisposable(SynchronizationContext.Current, disp);
        Thread.Sleep(TimeSpan.FromSeconds(5));
        Log("I'm going to dispose...");
        //dispSynced.Dispose();
        disp.Dispose();
        Log("Disposed has returned...");
        Console.ReadKey();
    }
}

When the above is ran I get:

[10] I am subscribing..
[10] I have returned from subscribing...
[11] Subscribing starting.. this will take a few seconds..
[6] I am polling...
[6] Processing 0
[6] I am polling...
[6] Processing 1
[10] I'm going to dispose...
[10] Disposed has returned...
[13] I am polling...
[6] I am polling...
[13] I am polling...
[14] Disposing is really done now!

So, all I'm trying to do is to have [10] Disposed has returned... be the last line printed indicating that the Dispose call is blocking.

The ContextDisposable that Rx ships with seems ideal for my use case but I don't know how to get a SynchronizationContext representing my current thread. Is there a way I can use ContextDisposable to do what I want or do I need a totally different approach?


回答1:


If you look at the source code for SubscribeOn it looks like the dispose function will be scheduled on the specified scheduler. Try something like this:

private static IObservable<long> GetObservable(IScheduler scheduler)
{
  return Observable.Create<long>(obs =>
  {
    var disposables = new CompositeDisposable();

    disposables.Add(
      Disposable.Create(() =>
      {
        Thread.Sleep(TimeSpan.FromSeconds(3));
        Log("Disposing is really done now!");
      }));

    disposables.Add(
      scheduler.Schedule(() =>
      {
        Log("Subscribing starting.. this will take a few seconds..");
        Thread.Sleep(TimeSpan.FromSeconds(2));
        disposables.Add(
          Observable.Interval(TimeSpan.FromSeconds(1)).Do(_ => Log("I am polling...")).Subscribe(obs));
      }));

    return disposables;
  });


private static void Main(string[] args)
{
  var foo = GetObservable(NewThreadScheduler.Default);

  Log("I am subscribing..");
  var disp = foo.Subscribe(i => Log("Processing " + i.ToString()));
  Log("I have returned from subscribing...");

  // SC.Current is null in a ConsoleApp :/  Can I get a SC that uses my current thread?
  //var dispSynced = new ContextDisposable(SynchronizationContext.Current, disp);
  Thread.Sleep(TimeSpan.FromSeconds(5));
  Log("I'm going to dispose...");
  //dispSynced.Dispose();
  disp.Dispose();
  Log("Disposed has returned...");
  Console.ReadKey();
}



回答2:


(starting from scratch - there's another way!)

This was an interesting thing to look into - truth be told, I've never had a need for this before, but there's an observable extension Observable.Synchronize:

This makes blocking quite trivial, although I'm not 100% sure this will apply to your use case...regardless, here's a modified main using this approach:

private static void Main(string[] args)
{
    var sync = new object();
    var foo = Observable.CreateWithDisposable<long>(obs =>
    {
        Log("Subscribing starting.. this will take a few seconds..");
        Thread.Sleep(TimeSpan.FromSeconds(2));
        var sub = Observable.Interval(TimeSpan.FromSeconds(1))
                        .Do(_ => Log("I am polling..."))
                        .Synchronize(sync)
                        .Subscribe(obs);
        return Disposable.Create(
            () =>
                {
                    lock (sync)
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(3));
                        sub.Dispose();
                        Log("Disposing is really done now!");                                
                    }
                });
    });

    Log("I am subscribing..");
    var disp = foo
        .SubscribeOn(Scheduler.NewThread)
        .Subscribe(i => Log("Processing " + i));

    Log("I have returned from subscribing...");
    Thread.Sleep(TimeSpan.FromSeconds(5));
    Log("I'm going to dispose...");
    disp.Dispose();
    Log("Disposed has returned...");
    Console.ReadKey();
}


来源:https://stackoverflow.com/questions/14131306/controlling-what-thread-an-rx-subscription-is-disposed-on-after-subscribedon-has

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