私は Tasks を使用してViewModelで長時間実行されているサーバー呼び出しを実行し、TaskScheduler.FromSyncronizationContext()
を使用してDispatcher
で結果をマーシャリングします。例えば:
var context = TaskScheduler.FromCurrentSynchronizationContext();
this.Message = "Loading...";
Task task = Task.Factory.StartNew(() => { ... })
.ContinueWith(x => this.Message = "Completed"
, context);
これは、アプリケーションを実行すると正常に機能します。しかし、NUnit
でResharper
テストを実行すると、FromCurrentSynchronizationContext
の呼び出しでエラーメッセージが表示されます。
現在のSynchronizationContextをTaskSchedulerとして使用することはできません。
これは、テストがワーカースレッドで実行されるためだと思います。テストがメインスレッドで実行されるようにするにはどうすればよいですか?他の提案は大歓迎です。
SynchronizationContextを提供する必要があります。これは私がそれを処理する方法です:
[SetUp]
public void TestSetUp()
{
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
Ritch Meltonのソリューションは私にはうまくいきませんでした。これは、私のTestInitialize
関数がテストと同様に非同期であるため、すべてのawait
で現在のSynchronizationContext
が失われるためです。これは、MSDNが指摘しているように、SynchronizationContext
クラスが「ダム」であり、すべての作業をスレッドプールにキューイングするだけだからです。
私のために働いたのは、実際にFromCurrentSynchronizationContext
がないとき(つまり、現在のコンテキストがnullの場合)SynchronizationContext
呼び出しをスキップすることです。 UIスレッドがない場合、そもそもそれと同期する必要はありません。
_TaskScheduler syncContextScheduler;
if (SynchronizationContext.Current != null)
{
syncContextScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
else
{
// If there is no SyncContext for this thread (e.g. we are in a unit test
// or console scenario instead of running in an app), then just use the
// default scheduler because there is no UI thread to sync with.
syncContextScheduler = TaskScheduler.Current;
}
_
このソリューションは、他の選択肢よりも簡単です。
TaskScheduler
をViewModelに渡します(依存性注入を介して)SynchronizationContext
と「偽の」UIスレッドを作成します-私にとってはもっと価値のあるトラブルスレッドのニュアンスの一部は失われますが、特定のスレッドでOnPropertyChangedコールバックがトリガーされることを明示的にテストしているわけではないので、大丈夫です。 new SynchronizationContext()
を使用する他の答えは、とにかくその目標に対して実際にはそれ以上良くなりません。
SynchronizationContextの動作を保証するために、複数のソリューションを組み合わせました。
using System;
using System.Threading;
using System.Threading.Tasks;
public class CustomSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback action, object state)
{
SendOrPostCallback actionWrap = (object state2) =>
{
SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());
action.Invoke(state2);
};
var callback = new WaitCallback(actionWrap.Invoke);
ThreadPool.QueueUserWorkItem(callback, state);
}
public override SynchronizationContext CreateCopy()
{
return new CustomSynchronizationContext();
}
public override void Send(SendOrPostCallback d, object state)
{
base.Send(d, state);
}
public override void OperationStarted()
{
base.OperationStarted();
}
public override void OperationCompleted()
{
base.OperationCompleted();
}
public static TaskScheduler GetSynchronizationContext() {
TaskScheduler taskScheduler = null;
try
{
taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
} catch {}
if (taskScheduler == null) {
try
{
taskScheduler = TaskScheduler.Current;
} catch {}
}
if (taskScheduler == null) {
try
{
var context = new CustomSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(context);
taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
} catch {}
}
return taskScheduler;
}
}
使用法:
var context = CustomSynchronizationContext.GetSynchronizationContext();
if (context != null)
{
Task.Factory
.StartNew(() => { ... })
.ContinueWith(x => { ... }, context);
}
else
{
Task.Factory
.StartNew(() => { ... })
.ContinueWith(x => { ... });
}