Поточний SynchronizationContext не може використовуватися як програмування завдань


98

Я використовую Завдання, щоб виконувати тривалі дзвінки на сервер у своєму 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.FromCurrentSynchronizationContext()всередині лямбда і виконання було відкладено на інший потік. виведення контексту поза лямбда вирішило проблему.
М.казем Ахгарі

Відповіді:


145

Вам потрібно надати SynchronizationContext. Ось як я впораюся з цим:

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

6
Для MSTest: введіть код вище у Метод, позначений ClassInitializeAttribute.
Даніель Бішар

6
@SACO: Насправді, я маю поставити його методом з TestInitializeAttribute, інакше проходить лише перший тест.
Торарін

2
Для тестів xunit я поміщаю його в статичний тип ctor, оскільки його потрібно встановлювати лише один раз за кріплення.
codekaizen

3
Я зовсім не розумію, чому ця відповідь була прийнята як рішення. ЦЕ НЕ ПРАЦЮЄ. І причина проста: SynchronizationContext - це фіктивний клас, функція відправлення / повідомлення якого марна. Цей клас повинен бути абстрактним, а не конкретним класом, який, можливо, приводить людей до помилкового відчуття "це працює". @tofutim Ви, мабуть, хочете надати власну реалізацію, похідну від SyncContext.
h9uest

1
Я думаю, я це зрозумів. Мій TestInitialize є асинхронним. Щоразу, коли в TestInit з'являється "очікування", поточний синхронізаційний контекст втрачається. Це відбувається тому, що (як зазначав @ h9uest) реалізація за замовчуванням SynchronizationContext просто ставить черги в завдання ThreadPool і насправді не продовжується в тій же нитці.
Сапф

24

Рішення Рітча Мелтона для мене не спрацювало. Це тому, що моя 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()насправді, нічим не підходять для цієї мети.


Ваш elseвипадок вийде з ладу і в додатку служби Windows, в результаті чогоsyncContextScheduler == null
FindOutIslamNow

Зустрічався з тією ж проблемою, але замість цього я прочитав вихідний код NUnit. AsyncToSyncAdapter лише переосмислює ваш SynchronizationContext, якщо він працює в потоці STA. Вирішення завдання - позначити свій клас [RequiresThread]атрибутом.
Арон

1

Я поєднав декілька рішень, щоб мати гарантію на роботу 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 => { ... });
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.