Текущий SynchronizationContext не может использоваться в качестве TaskScheduler

Я использую задачи для запуска длительных вызовов сервера в моей 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 as:

текущий SynchronizationContext не может использоваться в качестве TaskScheduler.

Я думаю, это потому, что тесты выполняются в рабочих потоках. Как я могу гарантировать, что тесты выполняются в основном потоке ? Любые другие предложения приветствуются.

3 ответов


вам нужно предоставить SynchronizationContext. Вот как я с этим справляюсь:--2-->

[SetUp]
public void TestSetUp()
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}

TestInitialize функция асинхронна, как и мои тесты, поэтому с каждым await текущего SynchronizationContext теряется. Это ведь как очки на MSDN наружу,SynchronizationContext класс "тупой" и просто очереди все работают в пул потоков.

то, что сработало для меня, на самом деле просто пропускает FromCurrentSynchronizationContext позвоните, когда нет SynchronizationContext (то есть, если текущий контекст null). Если нет потока 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 и "поддельный" поток пользовательского интерфейса для тестов, чтобы запустить на пути больше проблем для меня, что это стоит

я теряю некоторые нюансы резьбы, но я явно не тестирую, что мой 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 => { ... });
}