it-swarm.com.de

Der aktuelle SynchronizationContext darf nicht als TaskScheduler verwendet werden

Ich verwende Tasks , um in meinem ViewModel lange laufende Serveraufrufe auszuführen, und die Ergebnisse werden mit TaskScheduler.FromSyncronizationContext() auf Dispatcher zurückgeleitet. Zum Beispiel:

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

Dies funktioniert gut, wenn ich die Anwendung ausführen. Aber wenn ich meine NUnit Tests auf Resharper starte, erhalte ich die Fehlermeldung beim Aufruf von FromCurrentSynchronizationContext als: 

Der aktuelle SynchronizationContext darf nicht als TaskScheduler verwendet werden.

Ich denke, das liegt daran, dass die Tests auf Worker-Threads laufen. Wie kann ich sicherstellen, dass die Tests im Haupt-Thread ausgeführt werden? Irgendwelche anderen Vorschläge sind willkommen.

94
anivas

Sie müssen einen SynchronizationContext bereitstellen. So gehe ich damit um:

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

Die Lösung von Ritch Melton funktionierte nicht für mich. Dies liegt daran, dass meine TestInitialize-Funktion ebenso wie meine Tests async ist. Mit jeder await geht also die aktuelle SynchronizationContext verloren. Dies liegt daran, dass, wie MSDN darauf hinweist, die SynchronizationContext-Klasse "dumm" ist und alle Warteschlangen in den Thread-Pool einreihen.

Was für mich funktioniert hat, ist eigentlich das Überspringen des FromCurrentSynchronizationContext-Aufrufs, wenn es keine SynchronizationContext gibt (dh wenn der aktuelle Kontext null ist). Wenn es keinen UI-Thread gibt, muss ich mich überhaupt nicht damit synchronisieren.

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

Ich fand diese Lösung einfacher als die Alternativen, bei denen:

  • Übergeben einer TaskScheduler an das ViewModel (über Abhängigkeitseinspritzung)
  • Erstellen Sie einen Test SynchronizationContext und einen "gefälschten" UI-Thread, auf dem die Tests ausgeführt werden können - viel mehr Probleme für mich, die sich lohnen

Ich habe etwas von der Threading-Nuance verloren, aber ich teste nicht explizit, dass meine OnPropertyChanged-Callbacks auf einen bestimmten Thread auslösen, also bin ich damit einverstanden. Die anderen Antworten mit new SynchronizationContext() sind für dieses Ziel sowieso nicht besser.

18
Sapph

Ich habe mehrere Lösungen kombiniert, um eine Garantie für das Funktionieren von SynchronizationContext zu erhalten:

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

Verwendungszweck:

var context = CustomSynchronizationContext.GetSynchronizationContext();

if (context != null) 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... }, context);
}
else 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... });
}
0
Ujeenator