14

Mis preguntas son muchas. Desde que lo vi. NET 4.5, quedé muy impresionado. Lamentablemente, todos mis proyectos son .NET 4.0 y no estoy pensando en migrar. Entonces me gustaría simplificar mi código.Reemplazar los métodos que utilizan backgroundworker por async/.tpl (.NET 4.0)

En la actualidad, la mayor parte del código que suelen tener tiempo suficiente para congelar la pantalla, hago lo siguiente:

BackgroundWorker bd = new BackgroundWorker(); 
bd.DoWork += (a, r) => 
    { 
     r.Result = ProcessMethod(r.Argument); 
    }; 
bd.RunWorkerCompleted += (a, r) => 
    { 
     UpdateView(r.Result); 
    }; 

bd.RunWorkerAsync(args); 

Honestamente, estoy cansado de él. Y eso se convierte en un gran problema cuando hay una interacción compleja lógica del usuario.

Me pregunto, ¿cómo simplificar esta lógica? (Recuerde que estoy con. Net 4.0) Noté algunas cosas en google, pero no encontré nada fácil de implementar y adecuado para mis necesidades.

pensé por debajo de esta solución:

var foo = args as Foo; 
var result = AsyncHelper.CustomInvoke<Foo>(ProcessMethod, foo); 
UpdateView(result); 

public static class AsyncHelper 
{ 
    public static T CustomInvoke<T>(Func<T, T> func, T param) where T : class 
    { 
     T result = null; 
     DispatcherFrame frame = new DispatcherFrame(); 
     Task.Factory.StartNew(() => 
     { 
      result = func(param); 
      frame.Continue = false; 
     }); 

     Dispatcher.PushFrame(frame); 

     return result; 
    } 
} 

No estoy seguro sobre el impacto es en la manipulación de la estructura despachador. Pero sé que funcionaría muy bien, por ejemplo, podría usarlo en todos los eventos de controles sin molestarme en congelar la pantalla. Mi conocimiento sobre los tipos genéricos, la covarianza y la contravarianza es limitado, tal vez este código se puede mejorar.

Pensé en otras cosas usando Task.Factory.StartNew y Dispatcher.Invoke, pero nada que parezca interesante y fácil de usar. ¿Alguien puede darme algo de luz?

Respuesta

15

Debería utilizar la Biblioteca de tareas paralelas (TPL). La clave es especificar el TaskScheduler para el actual SynchronizationContext para cualquier continuación en la que actualice la UI. Por ejemplo:

Task.Factory.StartNew(() => 
{ 
    return ProcessMethod(yourArgument); 
}) 
.ContinueWith(antecedent => 
{ 
    UpdateView(antecedent.Result); 
}, 
TaskScheduler.FromCurrentSynchronizationContext()); 

Aparte de alguna manejo de excepciones al acceder a la propiedad del antecedente Result, eso es todo lo que hay es también él. Al utilizar FromCurrentSynchronizationContext(), se utilizará el Contrato de sincronización ambiental que proviene de WPF (es decir, DispatcherSynchronizationContext) para ejecutar la continuación. Esto es lo mismo que llamar al Dispatcher.[Begin]Invoke, pero está completamente abstraído de él.

Si quisiera obtener incluso un "limpiador", si controla ProcessMethod realmente reescribiría eso para devolver un Task y dejarlo como se hace girar (aún puede usar StartNew internamente). De esta forma, abstrae a la persona que llama de las decisiones de ejecución asincrónica que ProcessMethod desea hacer por sí solo y, en su lugar, solo tiene que preocuparse de encadenar una continuación para esperar el resultado.

ACTUALIZACIÓN 5/22/2013

Cabe señalar que con la llegada de .NET 4.5 y el soporte de idiomas asíncrono en C# esta técnica prescrita es obsoleto y se puede confiar simplemente en esas funciones a ejecutar una tarea específica usando await Task.Run y luego la ejecución después de eso tendrá lugar nuevamente en el hilo Dispatcher automágicamente. Entonces algo como esto:

MyResultType processingResult = await Task.Run(() => 
{ 
    return ProcessMethod(yourArgument); 
}); 

UpdateView(processingResult); 
+0

parece una buena opción para simplificar mi código, ya puedo imaginar cómo usarlo. Aunque, no habrá una solución q pueda continuar en la misma ejecución del método? –

+0

@ J.Lennon No sé lo suficiente sobre su implementación actual de cola de trabajo, pero ciertamente puede hacer que este concepto funcione con lo que sea. Si tiene control sobre la implementación, le recomendaría utilizar TPL DataFlow API (ActionBlock ) como mecanismo de puesta en cola. Sin embargo, eso es todo un post separado para entender todo eso. :) –

1

¿Qué tal encapsular el código que siempre es el mismo en un componente reutilizable? Puede crear un Freezable que implemente ICommand, exponga una propiedad de Type DoWorkEventHandler y una propiedad Result. En ICommand.Ejecutado, creará un BackgroundWorker y conectará los delegados para DoWork y Completed, utilizando el valor de DoWorkEventHandler como manejador de eventos, y manejando Completado de manera que establezca su propia propiedad Result para el resultado devuelto en el evento.

Debería configurar el componente en XAML, utilizando un convertidor para vincular la propiedad DoWorkEventHandler a un método en el ViewModel (supongo que tiene uno), y vincular su Vista a la propiedad Result del componente, por lo que se pone actualizado automáticamente cuando el resultado hace una notificación de cambio.

Las ventajas de esta solución son: es reutilizable, y funciona solo con XAML, por lo que no hay más código de pegamento en su ViewModel solo para manejar BackgroundWorkers. Si no necesita su proceso en segundo plano para informar el progreso, incluso podría ignorar que se ejecuta en un hilo de fondo, por lo que puede decidir en el XAML si desea llamar a un método de forma síncrona o asíncrona.

Cuestiones relacionadas