.net - truck - ¿Cómo puedo obtener un TaskScheduler para un Dispatcher?
programas para buscar cargas en usa (5)
Tengo una aplicación con varios Dispatcher
''s (también conocidos como hilos de la GUI, también conocido como bombas de mensajes) para garantizar que una parte lenta y sin respuesta de la GUI se ejecute sin afectar demasiado al resto de la aplicación. También uso mucho la Task
.
Actualmente tengo un código que ejecuta condicionalmente una Action
en un TaskScheduler
o un Dispatcher
y luego devuelve una Task
ya sea directamente o mediante la creación manual de uno usando TaskCompletionSource
. Sin embargo, este diseño de personalidad dividida hace que tratar con la cancelación, las excepciones, etc. sea mucho más complicado de lo que me gustaría. Quiero usar Task
s en todas partes y DispatcherOperation
s en ninguna parte. Para hacer eso necesito programar tareas en los despachadores, pero ¿cómo?
¿Cómo puedo obtener un TaskScheduler
para cualquier Dispatcher
dado?
Edit: Después de la discusión a continuación, me decidí por la siguiente implementación:
public static Task<TaskScheduler> GetScheduler(Dispatcher d) {
var schedulerResult = new TaskCompletionSource<TaskScheduler>();
d.BeginInvoke(() =>
schedulerResult.SetResult(
TaskScheduler.FromCurrentSynchronizationContext()));
return schedulerResult.Task;
}
Así es como siempre convierto una llamada de función asíncrona en una llamada de función de sincronización (felicitaciones a alguien en Internet):
public static class ThreadingUtils
{
public static TaskScheduler GetScheduler(Dispatcher dispatcher)
{
using (var waiter = new ManualResetEvent(false))
{
TaskScheduler scheduler = null;
dispatcher.BeginInvoke(new Action(() =>
{
scheduler =
TaskScheduler.FromCurrentSynchronizationContext();
waiter.Set();
}));
waiter.WaitOne();
return scheduler;
}
}
}
Variación:
if (!waiter.WaitOne(2000))
{
//Timeout connecting to server, log and exit
}
Desafortunadamente, no hay una forma integrada de hacer esto. No hay una clase integrada dedicada a envolver un Dispatcher
en un TaskScheduler
; lo más cercano que tenemos es la que envuelve un SynchronizationContext
. Y la única API pública para crear un TaskScheduler
partir de un SynchronizationContext
es la que Paul Michalik menciona: TaskScheduler.FromCurrentSynchronizationContext
- y como puede observar, eso solo funciona si ya se encuentra en el contexto de sincronización relevante (es decir, en el hilo relevante del despachador) .
Así que tienes tres opciones:
- Organice su código para que la clase que necesita programadores para los despachadores relevantes tenga la oportunidad de ejecutarse en los subprocesos de esos despachadores en algún momento, para que pueda usar
TaskScheduler.FromCurrentSynchronizationContext
según lo previsto. - Use
Dispatcher.BeginInvoke
para ejecutar algún código en el subproceso del distribuidor, y en ese código, llame aTaskScheduler.FromCurrentSynchronizationContext
. (En otras palabras, si no puedes hacer que 1. ocurra naturalmente, obliga a que suceda). - Escribe tu propio planificador de tareas.
Echa un vistazo a TaskScheduler.FromCurrentSynchronizationContext
. El marco de tareas proporciona una forma muy flexible de configurar la ejecución de las operaciones de proceso de cómputo incluso si la aplicación impone un modelo de subprocesamiento específico.
EDITAR:
Hm, es difícil obtener más explícito de lo que has publicado. Entiendo que está ejecutando una aplicación de vista múltiple con despachadores separados para cada vista, ¿verdad? Dado que todo el envío se reduce a obtener un SynchronizationContext
y Post
-ing en él, puede obtener el TaskScheduler
correcto (el que tiene el SynchronizationContext
correcto) en algún punto donde sus vistas obtuvieron uno. Una forma sencilla de hacerlo sería obtener un TaskScheduler durante la configuración de los taks (s):
// somewhere on GUI thread you wish to invoke
// a long running operation which returns an Int32 and posts
// its result in a control accessible via this.Text
(new Task<Int32>(DoSomeAsyncOperationReturningInt32)
.ContinueWith(tTask => this.Text = tTask.Result.ToString(),
TaskScheduler.FromCurrentSynchronizationContext)).Start();
No estoy seguro de si esto ayuda, si estás usando Tareas ampliamente, probablemente ya sabrás que ...
Paso 1: Crea un método de extensión:
public static Task<TaskScheduler> ToTaskSchedulerAsync (
this Dispatcher dispatcher,
DispatcherPriority priority = DispatcherPriority.Normal) {
var taskCompletionSource = new TaskCompletionSource<TaskScheduler> ();
var invocation = dispatcher.BeginInvoke (new Action (() =>
taskCompletionSource.SetResult (
TaskScheduler.FromCurrentSynchronizationContext ())), priority);
invocation.Aborted += (s, e) =>
taskCompletionSource.SetCanceled ();
return taskCompletionSource.Task;
}
Paso 2: Usa el método de extensión:
Sintaxis antigua :
var taskSchedulerAsync = Dispatcher.CurrentDispatcher.ToTaskSchedulerAsync ();
var taskFactoryAsync = taskSchedulerAsync.ContinueWith<TaskFactory> (_ =>
new TaskFactory (taskSchedulerAsync.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
// this is the only blocking statement, not needed once we have await
var taskFactory = taskFactoryAsync.Result;
var task = taskFactory.StartNew (() => { ... });
Nueva sintaxis :
var taskScheduler = await Dispatcher.CurrentDispatcher.ToTaskSchedulerAsync ();
var taskFactory = new TaskFactory (taskScheduler);
var task = taskFactory.StartNew (() => { ... });
Podrías haber escrito toda la función en una línea:
public static Task<TaskScheduler> ToTaskSchedulerAsync(this Dispatcher dispatcher,
DispatcherPriority priority = DispatcherPriority.Normal)
{
return dispatcher.InvokeAsync<TaskScheduler>(() =>
TaskScheduler.FromCurrentSynchronizationContext(), priority).Task;
}
y aquellos que están contentos con el subproceso de la interfaz de usuario predeterminada pueden encontrar lo siguiente para sobrevivir:
var ts = Application.Current.Dispatcher.Invoke<TaskScheduler>(() => TaskScheduler.FromCurrentSynchronizationContext());