Текущий SynchronizationContext не может использоваться в качестве TaskScheduler - PullRequest
95 голосов
/ 23 ноября 2011

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

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

Ответы [ 3 ]

139 голосов
/ 23 ноября 2011

Вам необходимо предоставить SynchronizationContext.Вот как я справляюсь:

[SetUp]
public void TestSetUp()
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
20 голосов
/ 10 апреля 2016

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

Для меня сработало просто пропустить вызов FromCurrentSynchronizationContext, когда нетSynchronizationContext (то есть, если текущий контекст ноль ).Если нет потока пользовательского интерфейса, мне не нужно синхронизироваться с ним.

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(), на самом деле не достигают большего успеха для этой цели.

0 голосов
/ 08 сентября 2017

Я объединил несколько решений, чтобы иметь гарантию на работу 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 => { ... });
}
...