问题
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