2012-04-10 17 views
7

Necesito crear el hilo que reemplazará la foto en la ventana Windows Forms, que espera ~ 1 segundo y restauro la foto anterior.Dormir tarea (System.Threading.Tasks)

pensé que el siguiente código:

TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext(); 
var task = Task.Factory.StartNew(() => 
{ 
    pic.Image = Properties.Resources.NEXT; 
    Thread.Sleep(1000); 
    pic.Image = Properties.Resources.PREV; 
}, CancellationToken.None, TaskCreationOptions.LongRunning, ui) 

hacer el trabajo, pero por desgracia no lo hace. Congela el hilo principal de la interfaz de usuario.

Eso es porque no se garantiza que haya un hilo por cada tarea. Un hilo puede usarse para procesar varias tareas. Incluso la opción TaskCreationOptions.LongRunning no puede ayudar.

¿Cómo puedo solucionarlo?

+8

Crea el programador de tareas utilizando 'FromCurrentSynchronizationContext()', que para WinForms será el subproceso de la interfaz de usuario. Por lo tanto, su tarea finalmente se ejecuta en el subproceso de interfaz de usuario, que luego se pone a dormir. * No ponga el hilo de UI a dormir. Ever. * – dlev

+0

Su código actualiza la interfaz de usuario directamente, por lo que debe ejecutarse en el hilo de la interfaz de usuario. Su código duerme, por lo que no se puede ejecutar en el hilo de la interfaz de usuario. Conclusión: su código está roto. Solucionarlo eliminando uno de los dos requisitos conflictivos. –

Respuesta

25

Thread.Sleep es un retraso síncrono. Si desea un retraso asincrónico, utilice Task.Delay.

En C# 5, que se encuentra actualmente en versión beta, sólo tiene que decir

await Task.Delay(whatever); 

en un método asincrónico, y el método captará automáticamente donde lo dejó.

Si no está utilizando C# 5, puede establecer "manualmente" el código que desee que sea la continuación de la demora.

+0

Estoy bastante seguro de que esto funciona bien en .net 4.5 :) –

+0

@JosephLennox C# 5 == .NET 4.5 –

5

Debería utilizar un Timer para realizar una tarea de IU en algún momento en el futuro. Solo configúrelo para que se ejecute una vez, y con un intervalo de 1 segundo. Coloque el código UI en el evento tick y luego actívelo.

Si realmente quería usar tareas, usted quiere tener la otra tarea no se ejecute en el hilo de interfaz de usuario, sino más bien en una amenaza fondo (es decir, sólo un StartNew tarea regular) y luego usar el Control.Invoke dentro de la tarea para ejecutar un comando en el hilo de la interfaz de usuario. El problema aquí es que está ayudando al problema subyacente de comenzar una tarea solo para que duerma. Es mejor tener el código ni siquiera ejecutado en primer lugar durante todo el segundo.

7

Cuando pasa un nuevo TaskScheduler que proviene del contexto de sincronización actual, en realidad le dice a la tarea que se ejecute en el subproceso de la interfaz de usuario. De hecho, quiere hacer eso, para que pueda actualizar el componente de la interfaz de usuario, sin embargo, no desea dormir en esa secuencia, ya que se bloqueará.

Este es un buen ejemplo de cuando .ContinueWith es ideal:

TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext(); 
var task = Task.Factory.StartNew(() => 
            { 
             pic.Image = Properties.Resources.NEXT; 
            }, 
           CancellationToken.None, 
           TaskCreationOptions.None, 
           ui); 

task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default) 
    .ContinueWith(t => 
         { 
          pic.Image = Properties.Resources.Prev; 
         }, ui); 

EDITAR (eliminado algunas cosas y añade este):

Lo que pasa es que estamos bloqueando el hilo de interfaz de usuario solo el tiempo suficiente para actualizar pic.Image. Al especificar el TaskScheduler, le indica en qué subproceso ejecutar la tarea. Es importante saber que la relación entre Tareas e Hilos no es 1: 1. De hecho, puede tener 1000 tareas ejecutándose en relativamente pocos hilos, 10 o menos, todo depende de la cantidad de trabajo que tenga cada tarea. No suponga que cada tarea que cree se ejecutará en un hilo separado. El CLR hace un excelente trabajo al equilibrar el rendimiento automáticamente.

Ahora, no tienes que usar el valor predeterminado TaskScheduler, como has visto. Cuando pasa la interfaz de usuario TaskScheduler, es decir, TaskScheduler.FromCurrentSynchronizationContext(), utiliza el subproceso de interfaz de usuario en lugar del grupo de subprocesos, como lo hace TaskScheduler.Default.

Teniendo esto en mente, vamos a revisar el código de nuevo:

var task = Task.Factory.StartNew(() => 
            { 
             pic.Image = Properties.Resources.NEXT; 
            }, 
           CancellationToken.None, 
           TaskCreationOptions.None, 
           ui); 

Aquí, estamos creando y comenzar una tarea que se ejecutará en el IU hilo, que se actualice la propiedad Image de pic con tu recurso. Mientras hace esto, UI no responderá.Afortunadamente, esta es probablemente una operación muy muy, y el usuario ni siquiera lo notará.

task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default) 
    .ContinueWith(t => 
         { 
          pic.Image = Properties.Resources.Prev; 
         }, ui); 

Con este código, estamos llamando el método ContinueWith. Hace exactamente lo que parece. Devuelve un nuevo objeto Task que ejecutará el parámetro lambda cuando se ejecute. Se iniciará cuando la tarea se haya completado, haya fallado o se haya cancelado. Puede controlar cuándo se ejecutará pasando en TaskContinuationOptions. Sin embargo, también aprobamos un programador de tareas diferente al que teníamos antes. Este es el programador de tareas predeterminado que ejecutará una tarea en un hilo de grupo de subprocesos, por lo tanto, NO bloqueará la UI. Esta tarea podría ejecutarse durante horas y su UI seguirá siendo receptiva (no lo permita), ya que se trata de un hilo separado del subproceso de interfaz de usuario con el que está interactuando.

También hemos llamado ContinueWith en las tareas que hemos configurado para ejecutar en el planificador de tareas predeterminado. Esta es la tarea que actualizará la imagen en el subproceso de interfaz de usuario nuevamente, ya que hemos pasado el mismo planificador de tareas de interfaz de usuario a la tarea de ejecución. Una vez que la tarea de subprocesamiento ha finalizado, llamará a esta en el subproceso de interfaz de usuario, bloqueándola durante un período de tiempo muy corto mientras se actualiza la imagen.

+0

No uso el temporizador porque de hecho tengo un gran 'TableLayoutPanel' con' PictureBox'es en él (no quiero tener tantos temporizadores como 'PictureBox'es). Coloqué el código de esta tarea en el controlador de eventos OnClick porque quiero intercambiar imágenes por 1 segundo. después de hacer clic en la imagen. –

+0

Tu código funciona muy bien, pero no entiendo por qué. La clave para lograr su solución es continuar la tarea con diferentes 'TaskScheduler' (' .Default' one). Qué significa eso? ¿Cambiar el contexto al contexto del hilo de subprocesos? Entonces, ¿paramos el resto de los hilos? Eso no está claro para mí. –

+0

@ patryk.beza - Tal vez mis ediciones lo harán más claro para usted. –

Cuestiones relacionadas