在寻找学习Rx的材料时,我发现了这一点:Reactive Extensions (Rx) Koans。来自介绍:
'Koan'的定义
Kōans是一个禅词,意思是一个人的启蒙或觉醒,通常是通过拼图或谜语。最常见的是“单手拍手的声音是什么?”
它由许多简短的测试用例组成,它们教授Rx的不同方面。
其中一个应该直观地通过,然而,它失败了。你能解释一下原因吗?
完全在这里:
[TestMethod]
[Timeout(___)] //"Fill in the blanks" - I tried several values, e.g. 4000. No changes.
public void AsynchronousRunInParallel()
{
Func<int, int> inc = (int x) =>
{
// I set a breakpoint here and it's never hit.
Thread.Sleep(1500);
return x + 1;
};
double result = 0;
var incAsync = Observable.FromAsyncPattern<int, int>(inc.BeginInvoke,
inc.EndInvoke);
incAsync(1).Merge(incAsync(9)).Sum()
.SubscribeOn(Scheduler.Immediate)
.Subscribe(n => result = n);
Assert.AreEqual(12, result);
//the failing message says: 'expected 12, got 0'
}
答案 0 :(得分:4)
简短回答:
在检查结果之前,你没有足够的时间让测试的异步部分执行。
更长的答案:
此测试执行的操作顺序有点像:
IObservable
IObservable
,这是两个异步调用的合并结果,汇总在一起Subscribe
生成的IObservable
,导致方法的两次异步调用Thread.Sleep
,因此异步调用被阻止!您可以通过多种方式“修复”此问题:
Thread.Sleep
BeginInvoke
将呼叫更改为同步,但这需要对整个测试进行重组HistoricalScheduler
代替Immediate
一个在尝试单元测试Rx时,强烈建议使用HistoricalScheduler
- 基本上,它允许您在虚拟时间内前后跳转,这是测试与时间相关的代码(如Rx查询)的关键功能:
var theTardis = new HistoricalScheduler();
Func<int, int> inc = (int x) =>
{
theTardis.Sleep(TimeSpan.FromMilliseconds(1500));
return x + 1;
};
double result = 0;
var incAsync = Observable.FromAsyncPattern<int, int>(inc.BeginInvoke,inc.EndInvoke);
incAsync(1).Merge(incAsync(9)).Sum()
.SubscribeOn(theTardis)
.Subscribe(n => result = n);
// To the FUTURE!
theTardis.AdvanceBy(TimeSpan.FromSeconds(5));
Assert.AreEqual(12, result);
答案 1 :(得分:3)
这是同步版本的样子 - 你所拥有的最直接的版本。 Single()
将阻塞,直到observable完成。阻止通常是你想要避免的,但如果你只是搞乱它就没问题。
public void AsynchronousRunInParallel()
{
Func<int, int> inc = (int x) =>
{
Thread.Sleep(1500);
return x + 1;
};
var incAsync = Observable.FromAsyncPattern<int, int>(inc.BeginInvoke,
inc.EndInvoke);
int sum = incAsync(1).Merge(incAsync(9)).Sum().Single();
Assert.AreEqual(12, sum);
}
使用await
:
public async Task AsynchronousRunInParallel()
{
Func<int, int> inc = (int x) =>
{
Thread.Sleep(1500);
return x + 1;
};
var incAsync = Observable.FromAsyncPattern<int, int>(inc.BeginInvoke,
inc.EndInvoke);
int sum = await incAsync(1).Merge(incAsync(9)).Sum();
Assert.AreEqual(12, sum);
}
最后是一个使用Rx Do()
的异步 - 如果说这是一个更大的操作的一部分就好了:
public async Task AsynchronousRunInParallel()
{
Func<int, int> inc = (int x) =>
{
Thread.Sleep(1500);
return x + 1;
};
var incAsync = Observable.FromAsyncPattern<int, int>(inc.BeginInvoke,
inc.EndInvoke);
await incAsync(1).Merge(incAsync(9)).Sum().Do(sum =>
{
Assert.AreEqual(12, sum);
});
}
答案 2 :(得分:2)
在我看来,它是源代码中的一个错误,AsynchronousRunInParallel缺少等待的结果,例如在TheBloodyHardAsyncInvokationPatter中使用。
使用FromAsyncPattern时,只在当前线程中同步执行BeginInvoke。将在ThreadPool上安排实际工作和结果处理。因此,只要安排了2个异步增量,就会立即执行Assert,而无需等待完成。
我添加了:
ThreadUtils.WaitUntil(() => result != 0.0);
所以结果如下:
incAsync(1).Merge(incAsync(9)).
Sum().SubscribeOn(Scheduler.Immediate).
Subscribe(n => result = n);
ThreadUtils.WaitUntil(() => result != 0.0);
Assert.AreEqual(12, result);
或者您可能希望将“订阅”替换为“运行”。它是Koan本身的辅助方法,它将使用手动事件等待:
incAsync(1).Merge(incAsync(9)).
Sum().SubscribeOn(Scheduler.Immediate).
Run(n => result = n);
Assert.AreEqual(12, result);