Aktualna SynchronizationContext nie może być używana jako TaskScheduler

Używam Zadań do uruchamiania długich wywołań serwera w moim ViewModel, a wyniki są przenoszone z powrotem na Dispatcher za pomocą TaskScheduler.FromSyncronizationContext(). Na przykład:

var context = TaskScheduler.FromCurrentSynchronizationContext();
this.Message = "Loading...";
Task task = Task.Factory.StartNew(() => { ... })
            .ContinueWith(x => this.Message = "Completed"
                          , context);

To działa dobrze, gdy uruchamiam aplikację. Ale kiedy uruchamiam moje testy {[3] } na Resharper dostaję komunikat o błędzie podczas połączenia do FromCurrentSynchronizationContext jako:

Bieżący SynchronizationContext nie może być używany jako TaskScheduler.

Myślę, że to dlatego, że testy są uruchamiane na wątkach roboczych. Jak mogę zapewnić testy są uruchamiane na głównym wątku ? Wszelkie inne sugestie są mile widziane.

3 answers

Musisz podać SynchronizationContext. Tak sobie z tym radzę:

[SetUp]
public void TestSetUp()
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
 135
Author: Ritch Melton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-23 16:55:25

Rozwiązanie Ritcha Meltona nie zadziałało. Dzieje się tak dlatego, że moja TestInitialize funkcja jest asynchroniczna, podobnie jak moje testy, więc z każdym await prąd SynchronizationContext jest tracony. Dzieje się tak dlatego, że jak podkreśla MSDN, klasa SynchronizationContext jest "głupia" i po prostu kolejkuje całą pracę do puli wątków.

To, co dla mnie działało, to po prostu pomijanie wywołania FromCurrentSynchronizationContext, gdy nie ma SynchronizationContext (to znaczy, jeśli bieżący kontekst to null ). Jeśli nie ma wątku UI, nie muszę z nim synchronizować w pierwsze miejsce.

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;
}

Znalazłem to rozwiązanie prostsze niż alternatywy, które gdzie:

    W ten sposób można uzyskać więcej informacji na temat tego, co się dzieje w ViewModel.]} W tym celu należy utworzyć nowy wątek, który pozwoli na uruchomienie testów w przyszłości, co będzie dla mnie bardziej kłopotliwe, niż jest to dla mnie warte.]}

Tracę niektóre niuanse wątków, ale nie testuję wyraźnie, że moje wywołania zwrotne OnPropertyChanged wyzwalają konkretny wątek, więc mi to nie przeszkadza. Inne odpowiedzi za pomocą new SynchronizationContext() i tak nie robią nic lepszego dla tego celu.

 16
Author: Sapph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-05 23:42:28

Połączyłem wiele rozwiązań, aby mieć gwarancję pracy 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;
    }
}

Użycie:

var context = CustomSynchronizationContext.GetSynchronizationContext();

if (context != null) 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... }, context);
}
else 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... });
}
 0
Author: Ujeenator,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-08 14:54:22