2011-11-23 14 views
87

Sto utilizzando Tasks per eseguire chiamate server con esecuzione prolungata nel mio ViewModel ei risultati sono stati sottoposti a marshalling su Dispatcher utilizzando TaskScheduler.FromSyncronizationContext(). Ad esempio:L'attuale SynchronizationContext non può essere utilizzato come TaskScheduler

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

Questo funziona correttamente quando si esegue l'applicazione. Ma quando ho eseguito i miei NUnit test su Resharper ottengo il messaggio di errore sulla chiamata a FromCurrentSynchronizationContext come:

Lo SynchronizationContext corrente non possono essere utilizzati come un TaskScheduler.

Suppongo che ciò avvenga perché i test vengono eseguiti sui thread di lavoro. Come posso garantire che i test vengano eseguiti sul thread principale? Qualsiasi altro suggerimento è benvenuto.

risposta

133

È necessario fornire un SynchronizationContext. Ecco come lo gestisco:

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

ha funzionato, grazie! – anivas

+5

Per MSTest: inserire il codice sopra nel Metodo contrassegnato con ClassInitializeAttribute. – SACO

+0

Buono a sapersi ... –

11

La soluzione di Ritch Melton non ha funzionato per me. Questo perché la mia funzione TestInitialize è asincrona, come lo sono i miei test, quindi con ogni await viene perso l'attuale SynchronizationContext. Questo perché, come sottolinea MSDN, la classe SynchronizationContext è "stupida" e tutte le code funzionano tutte nel pool di thread.

Quello che ha funzionato per me è in realtà solo saltando la chiamata FromCurrentSynchronizationContext quando non c'è un SynchronizationContext (vale a dire, se il contesto corrente è nulla). Se non c'è un thread UI, non ho bisogno di sincronizzarlo con esso in primo luogo.

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

ho trovato questa soluzione più semplice rispetto alle alternative, che dove:

  • passare un TaskScheduler al ViewModel (tramite iniezione di dipendenza)
  • creare un test SynchronizationContext e un'interfaccia utente "falso" thread per i test da eseguire su: più problemi per me che vale

Perdo una parte della sfumatura di threading, ma non lo sono test esplicitamente che i miei callback di OnPropertyChanged si attivano su un thread specifico, quindi sono d'accordo. Le altre risposte che usano lo new SynchronizationContext() in realtà non migliorano comunque per quell'obiettivo.

+0

Il tuo caso 'else' fallirà anche in un'app del servizio Windows, risultante' syncContextScheduler == null' – FindOutIslamNow

0

ho combinato soluzione multipla per avere garanzia per lavorare 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; 
    } 
} 

Usage:

var context = CustomSynchronizationContext.GetSynchronizationContext(); 

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