2010-12-02 9 views
174

¿Existe una forma 'estándar' de especificar que una continuación de tarea debe ejecutarse en el subproceso desde el que se creó la tarea inicial?Continuación de la tarea en el subproceso UI

Actualmente tengo el siguiente código: funciona, pero hacer un seguimiento del despachador y crear una segunda Acción parece una sobrecarga innecesaria.

dispatcher = Dispatcher.CurrentDispatcher; 
Task task = Task.Factory.StartNew(() => 
{ 
    DoLongRunningWork(); 
}); 

Task UITask= task.ContinueWith(() => 
{ 
    dispatcher.Invoke(new Action(() => 
    { 
     this.TextBlock1.Text = "Complete"; 
    } 
}); 
+0

En el caso de su ejemplo, puede usar 'Control.Invoke (Action)', es decir. 'TextBlock1.Invoke' en lugar de' dispatcher.Invoke' –

+0

Gracias @ColonelPanic, pero estaba usando WPF (como etiquetado), no winforms. –

Respuesta

286

Call la continuación con TaskScheduler.FromCurrentSynchronizationContext():

Task UITask= task.ContinueWith(() => 
    { 
    this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 

Esto es adecuado sólo si el contexto de ejecución actual es en el hilo de interfaz de usuario.

+33

Es válido solo si el contexto de ejecución actual está en el hilo de la interfaz de usuario. Si coloca este código dentro de otra Tarea, obtendrá InvalidOperationException (consulte [Excepciones] (http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.fromcurrentsynchronizationcontext (v = vs. 110) .aspx) sección) – stukselbax

+0

En .NET 4.5 La respuesta de Johan Larsson debe usarse como una forma estándar para la continuación de una tarea en el hilo de la interfaz de usuario. Simplemente escriba: aguarde Task.Run (DoLongRunningWork); this.TextBlock1.Text = "Completo"; Ver también: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx –

18

Si usted tiene un valor de retorno tiene que enviar a la interfaz de usuario puede utilizar la versión genérica de esta manera:

Esto se llama desde una MVVM modelo de vista en mi caso.

var updateManifest = Task<ShippingManifest>.Run(() => 
    { 
     Thread.Sleep(5000); // prove it's really working! 

     // GenerateManifest calls service and returns 'ShippingManifest' object 
     return GenerateManifest(); 
    }) 

    .ContinueWith(manifest => 
    { 
     // MVVM property 
     this.ShippingManifest = manifest.Result; 

     // or if you are not using MVVM... 
     // txtShippingManifest.Text = manifest.Result.ToString();  

     System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now); 

    }, TaskScheduler.FromCurrentSynchronizationContext()); 
+0

Supongo que = antes de GenerateManifest es un error tipográfico. –

+0

Sí - ¡Ya se fue! Gracias. –

9

Solo quería agregar esta versión porque es un hilo tan útil y creo que es una implementación muy simple. He utilizado esto varias veces en varios tipos si la aplicación multiproceso:

Task.Factory.StartNew(() => 
     { 
     DoLongRunningWork(); 
     Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
       { txt.Text = "Complete"; })); 
     }); 
+2

No se reduce el voto ya que esta es una solución viable en algunos escenarios; sin embargo, la respuesta aceptada es mucho mejor. Es independiente de la tecnología ('TaskScheduler' es parte de BCL,' Dispatcher' no) y se puede usar para componer complejas cadenas de tareas debido a que no tiene que preocuparse por ninguna operación asincrónica de "fuga y olvídese" (como 'BeginInvoke '). –

+0

@Kirill puede expandirse un poco, porque algunos subprocesos SO han declarado unánimemente que el despachador es el método correcto si usa WPF de WinForms: uno puede invocar una actualización GUI de forma asincrónica (utilizando BeginInvoke) o de forma síncrona (Invocar), aunque normalmente async se usa porque uno no querría bloquear un hilo de fondo solo para una actualización de la GUI. ¿FromCurrentSynchronizationContext no pone la tarea de continuación en la cola de mensajes del hilo principal de la misma manera que el despachador? – Dean

+0

algo seguro. Voy a abordar el problema 'Dispatcher' primero. 'Dispatcher.Invoke/BeginInvoke' es un concepto de WPF; su contraparte en WinForms sería 'Control.Invoke/BeginInvoke'. Entonces, ahora tiene que adaptar su código a la plataforma específica con la que está trabajando, lo que no sería el caso si fuera con 'TaskScheduler.FromCurrentSynchronizationContext' en primer lugar ya que es parte de la Biblioteca de clases base, y es por lo tanto ampliamente disponible. –

21

Con asíncrono que acaba de hacer:

await Task.Run(() => do some stuff); 
// continue doing stuff on the same context as before. 
// while it is the default it is nice to be explicit about it with: 
await Task.Run(() => do some stuff).ConfigureAwait(true); 

Sin embargo:

await Task.Run(() => do some stuff).ConfigureAwait(false); 
// continue doing stuff on the same thread as the task finished on. 
0

J UST escribir el código como (Pero usar ContinueWith es una buena práctica, no se preocupe por una sobrecarga innecesaria para el tiempo de ejecución)

Task task = Task.Factory.StartNew(() => 
{ 
    DoLongRunningWork(); 
    Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => 
    { 
     this.TextBlock1.Text = "Complete"; 
    } 
}); 

Deja Dispatcher código en el bloque finally si usted quiere asegurarse de que esto para ejecutar.

tratar de evitarTaskScheduler.FromCurrentSynchronizationContext() como del uso de esta interfaz de usuario Thread puede ser bloqueado por su actual Thread.

+0

Esto es efectivamente lo mismo que @Deans answer. –

+0

@GregSansom sí desde la perspectiva del código, pero también agregué algunas notas a continuación que también son muy importantes de conocer en este contexto y también están relacionadas con otras respuestas. me gusta. El uso de TaskScheduler como la respuesta que está marcada a la derecha dice, pero puede causar algunos problemas. :) –

+1

Entonces debería haber agregado sus notas como comentario no como una respuesta completa. – pix

Cuestiones relacionadas