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.
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
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. –