C# 从操作创建可取消的IObservable
我想创建一个实用程序方法,为一个只在订阅时调用的操作创建一个IObservable!它遵循SubscribeOn(…)指令。以下是我的实现,它基于我可以从中提取的内容和其他资源,但在一个特定情况下失败:C# 从操作创建可取消的IObservable,c#,system.reactive,C#,System.reactive,我想创建一个实用程序方法,为一个只在订阅时调用的操作创建一个IObservable!它遵循SubscribeOn(…)指令。以下是我的实现,它基于我可以从中提取的内容和其他资源,但在一个特定情况下失败: /// <summary> /// Makes an observable out of an action. Only at subscription the task will be executed. /// </summary>
/// <summary>
/// Makes an observable out of an action. Only at subscription the task will be executed.
/// </summary>
/// <param name="action">The action.</param>
/// <returns></returns>
public static IObservable<Unit> MakeObservable_2(Action action)
{
return Observable.Create<Unit>(
observer =>
{
return System.Reactive.Concurrency.CurrentThreadScheduler.Instance.Schedule(
() =>
{
try
{
action();
observer.OnNext(Unit.Default);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
});
});
}
我认为这是可以观察到的。开始是你要寻找的。
我认为您可以使代码更简单,也可以使测试更简单。Rx的妙处在于,您应该能够消除所有任务/线程/手动重置事件。我还假设您也可以使用NUnit的[Timeout]属性而不是自定义代码 无论如何。。。 @Per是正确的,可观察的。Start是您所寻找的。你给它一个动作和一个IScheduler,这似乎正是你想要的
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObStart()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
var subscription = Observable.Start(action, scheduler)
.Subscribe();
Assert.IsFalse(flag);
scheduler.AdvanceBy(1);
Assert.IsTrue(flag);
subscription.Dispose(); //Not required as the sequence will have completed and then auto-detached.
}
然而,你可能会注意到它确实有一些奇怪的行为(至少在我这台电脑上的V1中)。具体地说,Observable.Start将立即运行操作,而不是实际等待订阅Observable序列。同样由于这个原因,调用subscribe,然后在应该执行的操作之前处理订阅是无效的。嗯
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObStart_dispose()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
var subscription = Observable.Start(action, scheduler).Subscribe();
Assert.IsFalse(flag);
subscription.Dispose();
scheduler.AdvanceBy(1);
Assert.IsFalse(flag); //FAILS. Oh no! this is true!
}
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObStart_no_subscribe()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
Observable.Start(action, scheduler);
//Note the lack of subscribe?!
Assert.IsFalse(flag);
scheduler.AdvanceBy(1);
Assert.IsFalse(flag);//FAILS. Oh no! this is true!
}
但是,我们可以按照您的方式使用Observable.Create。但是,您非常接近,不需要在Create委托中执行任何调度。只要相信Rx会为你做这件事
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObCreate()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
var subscription = Observable.Create<Unit>(observer =>
{
try
{
action();
observer.OnNext(Unit.Default);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
return Disposable.Empty;
})
.SubscribeOn(scheduler)
.Subscribe(); //Without subscribe, the action wont run.
Assert.IsFalse(flag);
scheduler.AdvanceBy(1);
Assert.IsTrue(flag);
subscription.Dispose(); //Not required as the sequence will have completed and then auto-detached.
}
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObCreate_dispose()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
var subscription = Observable.Create<Unit>(observer =>
{
try
{
action();
observer.OnNext(Unit.Default);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
return Disposable.Empty;
})
.SubscribeOn(scheduler)
.Subscribe(); //Without subscribe, the action wont run.
Assert.IsFalse(flag);
subscription.Dispose();
scheduler.AdvanceBy(1);
Assert.IsFalse(flag); //Subscription was disposed before the scheduler was able to run, so the action did not run.
}
如果你运行这个,你会注意到控制台上甚至没有打印任何内容,因此我们没有竞争条件,操作就是从不运行。原因是调度器尚未启动其消息循环。为了纠正这个测试,我们必须用混乱的基础结构代码填充它
[Test, Timeout(2000)]
public void Testing_with_Dispatcher_BeginInvoke()
{
var frame = new DispatcherFrame(); //1 - The Message loop
var wasRun = false;
Action MyAction = () =>
{
Console.WriteLine("Running...");
wasRun = true;
Console.WriteLine("Run.");
frame.Continue = false; //2 - Stop the message loop, else we hang forever
};
Dispatcher.CurrentDispatcher.BeginInvoke(MyAction);
Dispatcher.PushFrame(frame); //3 - Start the message loop
Assert.IsTrue(wasRun);
}
因此,我们显然不希望对所有需要WPF中并发性的测试都这样做。试图将frame.Continue=false注入到我们无法控制的操作中是一场噩梦。幸运的是,IScheudler通过它的调度方法公开了我们所需要的一切
接下来,CurrentThreadScheduler应该被看作是一个蹦床,而不是一个SynchronizationContext(我想你是这么认为的)。非常感谢您提供的详细信息。我将您的答案标记为有用,特别是对于测试调度程序演示。然而,我仍然不是很满意,我相信我的大部分问题仍然没有答案。虽然我同意我可以取消Task/Thread/ManualResetEvent,但我相信我的单元测试在代码和公式化的期望方面是正确的(你同意还是看到了任何问题?),我真的希望它们通过。同样的测试在MakeObservable_3中失败。这很关键,因为我认为
.SubscribeOn(Dispatcher.CurrentDispatcher)
应该正确执行。也许我还没有完全理解Rx。我不喜欢Observable.Create解决方案的一个事实是,try块的内容是在Disposable.Empty返回之前执行的。这种方法从不允许在订阅过程中通过Dispose()
取消某些内容。Observable.Start
-bug在2.0.20612.0中仍然可用。您是否有解释为什么Observable.SubscribeOn(Dispatcher.CurrentDispatcher.Subscribe())
在RxActionUtilities\u MakeObservableFromAction\u worksaseExpected()中失败。
我要求太多了吗?MakeObservable(Action Action,IsScheduler scheduler)
代替MakeObservable
+SubscribeOn
是更好的方法吗?嗯。公共静态IObservable MakeObservable_4(Action Action Action){返回Observable.Create(Observable=>{try{return Observable.Start(action.Subscribe(observer);}catch(Exception ex){observer.OnError(ex);return Disposable.Empty;}}}}
未能通过相同的单元测试…我认为您应该使用Rx/IsScheduler并利用取消和递归功能(),或者,如果需要,您应该使用Task/CancellationToken并可能转换为Rx with Task.ToObservable()。
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObStart_dispose()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
var subscription = Observable.Start(action, scheduler).Subscribe();
Assert.IsFalse(flag);
subscription.Dispose();
scheduler.AdvanceBy(1);
Assert.IsFalse(flag); //FAILS. Oh no! this is true!
}
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObStart_no_subscribe()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
Observable.Start(action, scheduler);
//Note the lack of subscribe?!
Assert.IsFalse(flag);
scheduler.AdvanceBy(1);
Assert.IsFalse(flag);//FAILS. Oh no! this is true!
}
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObCreate()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
var subscription = Observable.Create<Unit>(observer =>
{
try
{
action();
observer.OnNext(Unit.Default);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
return Disposable.Empty;
})
.SubscribeOn(scheduler)
.Subscribe(); //Without subscribe, the action wont run.
Assert.IsFalse(flag);
scheduler.AdvanceBy(1);
Assert.IsTrue(flag);
subscription.Dispose(); //Not required as the sequence will have completed and then auto-detached.
}
[Test]
public void Run_Action_as_IOb_on_scheduler_with_ObCreate_dispose()
{
var scheduler = new TestScheduler();
var flag = false;
Action action = () => { flag = true; };
var subscription = Observable.Create<Unit>(observer =>
{
try
{
action();
observer.OnNext(Unit.Default);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
return Disposable.Empty;
})
.SubscribeOn(scheduler)
.Subscribe(); //Without subscribe, the action wont run.
Assert.IsFalse(flag);
subscription.Dispose();
scheduler.AdvanceBy(1);
Assert.IsFalse(flag); //Subscription was disposed before the scheduler was able to run, so the action did not run.
}
public static class RxActionUtilities
{
/// <summary>
/// Makes an observable out of an action. Only at subscription the task will be executed.
/// </summary>
/// <param name="action">The action.</param>
/// <returns></returns>
/// <example>
/// <code>
/// <![CDATA[
/// RxActionUtilities.MakeObservable_3(myAction)
/// .SubscribeOn(_schedulerProvider.TaskPoolScheduler)
/// .Subscribe(....);
///
/// ]]>
/// </code>
/// </example>
public static IObservable<Unit> MakeObservable_3(Action action)
{
return Observable.Create<Unit>(observer =>
{
try
{
action();
observer.OnNext(Unit.Default);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
return Disposable.Empty;
});
}
}
[Test, Timeout(2000)]
public void DispatcherFail()
{
var wasRun = false;
Action MyAction = () =>
{
Console.WriteLine("Running...");
wasRun = true;
Console.WriteLine("Run.");
};
Dispatcher.CurrentDispatcher.BeginInvoke(MyAction);
Assert.IsTrue(wasRun);
}
[Test, Timeout(2000)]
public void Testing_with_Dispatcher_BeginInvoke()
{
var frame = new DispatcherFrame(); //1 - The Message loop
var wasRun = false;
Action MyAction = () =>
{
Console.WriteLine("Running...");
wasRun = true;
Console.WriteLine("Run.");
frame.Continue = false; //2 - Stop the message loop, else we hang forever
};
Dispatcher.CurrentDispatcher.BeginInvoke(MyAction);
Dispatcher.PushFrame(frame); //3 - Start the message loop
Assert.IsTrue(wasRun);
}