Ответ 1
Вам необходимо предоставить SynchronizationContext. Вот как я справляюсь с этим:
[SetUp]
public void TestSetUp()
{
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
Я использую Tasks для запуска длинных вызовов сервера в моей модели ViewModel, и результаты снова сортируются по Dispatcher
с помощью TaskScheduler.FromSyncronizationContext()
. Например:
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
, когда нет SynchronizationContext
(то есть, если текущий контекст null). Если нет потока пользовательского интерфейса, мне не нужно сначала синхронизировать его.
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
и "поддельный" пользовательский интерфейс для тестов, которые будут выполняться в пути больше, для меня, что стоитЯ теряю часть нюансов потоков, но я не проверяю, что мои вызовы 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 => { ... });
}