103 votes

Le contexte de synchronisation actuel ne peut pas être utilisé comme planificateur de tâches.

J'utilise Tâches pour exécuter des appels de serveur de longue durée dans mon ViewModel et les résultats sont marshallés en retour sur le Dispatcher en utilisant TaskScheduler.FromSyncronizationContext() . Par exemple :

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

Cela fonctionne correctement lorsque j'exécute l'application. Mais lorsque j'exécute mon NUnit tests sur Resharper J'obtiens le message d'erreur lors de l'appel à FromCurrentSynchronizationContext comme :

Le contexte de synchronisation actuel ne peut pas être utilisé comme planificateur de tâches.

Je suppose que c'est parce que les tests sont exécutés sur des threads de travail. Comment puis-je m'assurer que les tests sont exécutés sur le thread principal ? Toute autre suggestion est la bienvenue.

0 votes

Dans mon cas, j'ai utilisé TaskScheduler.FromCurrentSynchronizationContext() à l'intérieur d'une lambda et l'exécution a été reportée à un autre thread. l'obtention du contexte en dehors de la lambda a résolu le problème.

148voto

Ritch Melton Points 7173

Vous devez fournir un contexte de synchronisation. Voici comment je procède :

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

6 votes

Pour MSTest : placez le code ci-dessus dans la méthode marquée par l'attribut ClassInitializeAttribute.

6 votes

@SACO : En fait, je dois le mettre dans une méthode avec TestInitializeAttribute sinon seul le premier test est réussi.

0 votes

J'utilise ncrunch, c'est peut-être le problème ?

30voto

Sapph Points 4040

La solution de Ritch Melton n'a pas fonctionné pour moi. C'est parce que mon TestInitialize est asynchrone, tout comme mes tests, donc à chaque fois que la fonction await l'actuel SynchronizationContext est perdue. En effet, comme le souligne le MSDN, la fonction SynchronizationContext est "stupide" et se contente de mettre en file d'attente tous les travaux dans le pool de threads.

Ce qui a fonctionné pour moi, c'est d'ignorer la rubrique FromCurrentSynchronizationContext lorsqu'il n'y a pas de SynchronizationContext (c'est-à-dire si le contexte actuel est nul ). S'il n'y a pas de thread d'interface utilisateur, je n'ai pas besoin de me synchroniser avec lui en premier lieu.

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

J'ai trouvé cette solution plus simple que les autres, qui étaient les suivantes

  • Passer un TaskScheduler au ViewModel (via l'injection de dépendance)
  • Créer un test SynchronizationContext et un "faux" thread d'interface utilisateur pour que les tests s'exécutent - beaucoup plus de problèmes pour moi que cela n'en vaut la peine

Je perds une partie de la nuance de threading, mais je ne teste pas explicitement que mes callbacks OnPropertyChanged se déclenchent sur un thread spécifique, donc je suis d'accord avec cela. Les autres réponses utilisant new SynchronizationContext() ne font pas vraiment mieux pour cet objectif de toute façon.

1voto

Amber de Black Points 244

J'ai combiné plusieurs solutions pour garantir le fonctionnement de 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;
    }
}

Utilisation :

var context = CustomSynchronizationContext.GetSynchronizationContext();

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X