我需要按顺序排列两个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)});
然而,上述方法无效。基于我的日志记录_Disposable
和ContextDisposable
SomeService
在同一个线程上调用,但ContextDisposable
仍然发生在不同的线程上,并且服务正在被处理(和从而导致竞争条件和NPEs)。
我只用了几个星期编写C#,所以我确定问题是我对上下文和调度程序的工作方式的误解。解决这个问题的正确方法是什么?
答案 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会安排对Subscribe
和Dispose
的调用。因此,在订阅变量上调用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;
});
}
}