scheduled - crear tareas programadas desde c#
El SynchronizationContext actual no se puede usar como TaskScheduler (3)
Debe proporcionar un SynchronizationContext. Así es como lo manejo:
[SetUp]
public void TestSetUp()
{
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
Estoy usando Tasks para ejecutar llamadas de servidor en ejecución larga en mi ViewModel y los resultados se TaskScheduler.FromSyncronizationContext()
en Dispatcher
usando TaskScheduler.FromSyncronizationContext()
. Por ejemplo:
var context = TaskScheduler.FromCurrentSynchronizationContext();
this.Message = "Loading...";
Task task = Task.Factory.StartNew(() => { ... })
.ContinueWith(x => this.Message = "Completed"
, context);
Esto funciona bien cuando ejecuto la aplicación. Pero cuando ejecuto mis pruebas de NUnit
en Resharper
obtengo el mensaje de error en la llamada a FromCurrentSynchronizationContext
como:
El SynchronizationContext actual no se puede usar como TaskScheduler.
Supongo que esto se debe a que las pruebas se ejecutan en subprocesos de trabajo. ¿Cómo puedo asegurarme de que las pruebas se ejecuten en el hilo principal? Cualquier otra sugerencia es bienvenida.
He combinado varias soluciones para tener la garantía de trabajar 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;
}
}
Uso:
var context = CustomSynchronizationContext.GetSynchronizationContext();
if (context != null)
{
Task.Factory
.StartNew(() => { ... })
.ContinueWith(x => { ... }, context);
}
else
{
Task.Factory
.StartNew(() => { ... })
.ContinueWith(x => { ... });
}
La solución de Ritch Melton no funcionó para mí. Esto se debe a que mi función TestInitialize
es asíncrona, al igual que mis pruebas, por lo que con cada await
se pierde el actual SynchronizationContext
. Esto se debe a que, como señala MSDN, la clase SynchronizationContext
es "tonta" y simplemente pone en cola todo el trabajo en el grupo de subprocesos.
Lo que funcionó para mí en realidad es omitir la llamada FromCurrentSynchronizationContext
cuando no hay un SynchronizationContext
(es decir, si el contexto actual es nulo ). Si no hay un subproceso de interfaz de usuario, no necesito sincronizarme con él en primer lugar.
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;
}
Encontré esta solución más directa que las alternativas, que fueron:
- Pase un
TaskScheduler
al ViewModel (a través de la inyección de dependencia) - Cree un
SynchronizationContext
prueba y un hilo de interfaz de usuario "falso" para que se ejecuten las pruebas, lo cual es mucho más problemático para mí que valga la pena
Perdí algunos de los matices de subprocesos, pero no estoy probando explícitamente que mis callbacks OnPropertyChanged se activen en un hilo específico, así que estoy de acuerdo con eso. Las otras respuestas que usan el new SynchronizationContext()
realmente no mejoran para ese objetivo de todos modos.