Task.Delay のラッパーを作成する alexey anwser に基づいて、Reactive Extensions の IScheduler を使用する Task.Delay を作成する方法を次に示します。これにより、仮想時間を使用して遅延をテストできます。
using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
public static class TaskEx
{
public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken = default(CancellationToken))
{
#if TEST
return Observable.Timer(TimeSpan.FromMilliseconds(millisecondsDelay), AppContext.DefaultScheduler).ToTask(cancellationToken);
#else
return Task.Delay(millisecondsDelay, cancellationToken);
#endif
}
}
これは、単体テストを行っていない場合、コンパイル シンボルを使用して Rx を完全に回避します。
AppContext は、スケジューラを参照する単なるコンテキスト オブジェクトです。テストではAppContext.DefaultScheduler = testScheduler
、仮想タイム スケジューラによって遅延が発生するように設定できます。
ただし注意点があります。TestScheduler は同期的であるため、タスクを開始して内部で TaskEx.Delay を使用することはできません。これは、タスクがスケジュールされる前にスケジューラが進むためです。
var scheduler = new TestScheduler();
AppContext.DefaultScheduler = scheduler;
Task.Run(async () => {
await TaskEx.Delay(100);
Console.Write("Done");
});
/// this won't work, Task.Delay didn't run yet.
scheduler.AdvanceBy(1);
代わりに、常に を使用してタスクを開始する必要があるObservable.Start(task, scheduler)
ため、タスクは次の順序で実行されます。
var scheduler = new TestScheduler();
AppContext.DefaultScheduler = scheduler;
Observable.Start(async () => {
await TaskEx.Delay(100);
Console.Write("Done");
}, scheduler);
/// this runs the code to schedule de delay
scheduler.AdvanceBy(1);
/// this actually runs until the delay is complete
scheduler.AdvanceBy(TimeSpan.FromMilliseconds(100).Ticks);
これは確かにややこしいので、Task.Delay を使用するすべての場所では使用しません。ただし、遅延がアプリの動作を変更する特定のコードがいくつかあり、それをテストする必要があるため、これらの特殊なケースに役立ちます。