我想创建一个实用程序方法,为Action创建一个IObservable,只在订阅时AND!它遵循SubscribeOn(...)指令。这是我的实现,它基于我可以从http://www.introtorx.com和其他资源中提取的内容,但它在一个特定情况下失败:
/// <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);
}
});
});
}
我希望使用CurrrentThreadScheduler会导致使用SubscribeOn()中给出的Scheduler。此实现适用于.SubscribeOn(TaskPoolScheduler.Default),但不适用于.SubscribeOn(Dispatcher.CurrentDispatcher)。您可以更改上面的实施,以便下面的所有单元测试都通过吗?
[Test]
public void RxActionUtilities_MakeObservableFromAction_WorksAsExpected()
{
ManualResetEvent evt = new ManualResetEvent(false);
// Timeout of this test if sth. goes wrong below
Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Test timed out!");
evt.Set();
});
int threadIdOfAction = -42;
int threadIdOfSubscriptionContect = -43;
bool subscriptionWasCalled = false;
Action action = () =>
{
threadIdOfAction = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("This is an action on thread " + threadIdOfAction);
};
var observable = RxActionUtilities.MakeObservable_2(action);
threadIdOfSubscriptionContect = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Before subscription on thread " + threadIdOfSubscriptionContect);
// The next line is the one I want to have working, but the subscription is never executed
observable.SubscribeOn(Dispatcher.CurrentDispatcher).Subscribe(
//observable.Subscribe( // would pass
(unit) =>
{
Console.WriteLine("Subscription: OnNext " + threadIdOfAction + ", " + threadIdOfSubscriptionContect);
subscriptionWasCalled = true;
},
(ex) => evt.Set(), () => evt.Set());
Console.WriteLine("After subscription");
evt.WaitOne();
Assert.AreNotEqual(-42, threadIdOfAction);
Assert.AreNotEqual(-43, threadIdOfSubscriptionContect);
Assert.AreEqual(threadIdOfAction, threadIdOfSubscriptionContect);
Assert.That(subscriptionWasCalled);
}
[Test]
// This test passes with the current implementation
public void RxActionUtilities_MakeObservableFromActionSubscribeOnDifferentThread_WorksAsExpected()
{
ManualResetEvent evt = new ManualResetEvent(false);
// Timeout of this test if sth. goes wrong below
Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Test timed out!");
evt.Set();
});
int threadIdOfAction = 42;
int threadIdOfSubscriptionContect = 43;
bool subscriptionWasCalled = false;
Action action = () =>
{
threadIdOfAction = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("This is an action on thread " + threadIdOfAction);
};
var observable = RxActionUtilities.MakeObservable_2(action);
threadIdOfSubscriptionContect = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Before subscription on thread " + threadIdOfSubscriptionContect);
// The next line is the one I want to have working, but the subscription is never executed
observable.SubscribeOn(TaskPoolScheduler.Default).Subscribe(
(unit) =>
{
Console.WriteLine("Subscription: OnNext " + threadIdOfAction + ", " + threadIdOfSubscriptionContect);
subscriptionWasCalled = true;
},
(ex) => evt.Set(), () => evt.Set());
evt.WaitOne();
Console.WriteLine("After subscription");
Assert.AreNotEqual(-42, threadIdOfAction);
Assert.AreNotEqual(-43, threadIdOfSubscriptionContect);
Assert.AreNotEqual(threadIdOfAction, threadIdOfSubscriptionContect);
Assert.That(subscriptionWasCalled);
}
[Test]
public void RxActionUtilities_MakeObservableFromAction_IsCancellable()
{
ManualResetEvent evt = new ManualResetEvent(false);
// Timeout of this test if sth. goes wrong below
Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Test timed out!");
evt.Set();
});
int threadIdOfAction = -42;
int threadIdOfSubscriptionContect = -43;
bool subscriptionWasCalled = false;
bool actionTerminated = false;
Action action = () =>
{
threadIdOfAction = Thread.CurrentThread.ManagedThreadId;
for (int i = 0; i < 10; ++i)
{
Console.WriteLine("Some action #" + i);
Thread.Sleep(200);
}
actionTerminated = true;
evt.Set();
};
var observable = RxActionUtilities.MakeObservable_2(action);
threadIdOfSubscriptionContect = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Before subscription on thread " + threadIdOfSubscriptionContect);
var subscription =
observable.SubscribeOn(TaskPoolScheduler.Default).Subscribe(
(unit) =>
{
Console.WriteLine("Subscription: OnNext " + threadIdOfAction + ", " + threadIdOfSubscriptionContect);
subscriptionWasCalled = true;
},
(ex) => evt.Set(), () => evt.Set());
Console.WriteLine("After subscription");
Thread.Sleep(1000);
Console.WriteLine("Killing subscription ...");
subscription.Dispose();
Console.WriteLine("... done.");
evt.WaitOne();
Assert.IsFalse(actionTerminated);
Assert.AreNotEqual(-42, threadIdOfAction);
Assert.AreNotEqual(-43, threadIdOfSubscriptionContect);
Assert.AreEqual(threadIdOfAction, threadIdOfSubscriptionContect);
Assert.That(subscriptionWasCalled);
}
为了回应李的精心解答,我再试一次并重新提出我的问题。 IIUC我们可以总结一下
为了创建可取消的内容,我们需要一个知道取消的操作,例如使用Action<CancellationToken>
。这是我的下一次尝试。请告诉我您是否认为此实现非常适合Rx框架,或者我们是否可以再次改进:
public static IObservable<Unit>
MakeObservable(Action<CancellationToken> action, IScheduler scheduler)
{
return Observable.Create<Unit>(
observer
=>
{
// internally creates a new CancellationTokenSource
var cancel = new CancellationDisposable();
var scheduledAction = scheduler.Schedule(() =>
{
try
{
action(cancel.Token);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
});
// Cancellation before execution of action is performed
// by disposing scheduledAction
// Cancellation during execution of action is performed
// by disposing cancel
return new CompositeDisposable(cancel, scheduledAction);
});
}
如果你在这里:我无法弄清楚如何使用TestScheduler
来测试这个:
[Test]
public void MakeObservableFromCancelableAction_CancellationTakesPlaceWithTrueThread()
{
var scheduler = NewThreadScheduler.Default;
Action<CancellationToken> action =
(cancellationToken) =>
{
for (int i = 0; i < 10; ++i)
{
Console.WriteLine("Some action #" + i);
if (cancellationToken.IsCancellationRequested)
{
break;
}
Thread.Sleep(20);
// Hoping that the disposal of the subscription stops
// the loop before we reach i == 4.
Assert.Less(i, 4);
}
};
var observable = RxActionUtilities.MakeObservable(action, scheduler);
var subscription = observable.Subscribe((unit) => { });
Thread.Sleep(60);
subscription.Dispose();
}
答案 0 :(得分:2)
我认为你可以让你的代码更简单,你也可以让你的测试更简单。 Rx的美妙之处在于你应该能够取消所有的Task / Thread / ManualResetEvent。另外我假设您也可以使用NUnit的[Timeout]属性而不是自定义代码。
总之... @Per是对的,Observable.Start就是你要找的。你传递了一个Action和一个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.
}
但是你可能会注意到它确实有一些奇怪的行为(至少在这台PC上有V1)。具体来说,Observable.Start将立即运行Action,而不是实际等待订阅可观察序列。同样由于这个原因,调用subscribe,然后在执行操作之前处理订阅也没有效果。 Hmmmmm。
[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 delegate中进行任何调度。只要相信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.
}
如果您希望能够在正在处理的操作中途取消实际操作,那么您将需要执行一些比此更高级的操作。
最终的实施很简单:
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;
});
}
}
我希望有所帮助。
编辑: W.r.t你在单元测试中使用Dispatcher。我认为首先你应该尝试在应用另一层(Rx)之前了解它是如何工作的,以增加混乱。在WPF中编码时,Rx给我带来的一个主要好处是通过调度程序对Dispatcher进行抽象。它允许我轻松地在WPF中测试并发性。例如,这个简单的测试失败了:
[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);
}
所以我们显然不希望为所有需要在WPF中进行并发的测试执行此操作。尝试将frame.Continue = false注入我们无法控制的动作将是一场噩梦。幸运的是,IScheudler通过它的Schedule方法公开了我们所需要的一切。
下一个CurrentThreadScheduler应该被认为是一个Trampoline,而不是一个SynchronizationContext(我认为你认为它是这样)。
答案 1 :(得分:-1)
我认为Observable.Start就是你要找的。 http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.start(v=vs.103).aspx