¿Por qué TaskScheduler? ¿El actual TaskScheduler actual?
La Biblioteca paralela de tareas es excelente y la he usado mucho en los últimos meses. Sin embargo, hay algo que realmente me molesta: el hecho de queTaskScheduler.Current
es el programador de tareas predeterminado, noTaskScheduler.Default
. Esto no es absolutamente obvio a primera vista en la documentación ni en las muestras.
Current
puede provocar errores sutiles, ya que su comportamiento cambia dependiendo de si estás dentro de otra tarea. Lo cual no se puede determinar fácilmente.
Suponga que estoy escribiendo una biblioteca de métodos asincrónicos, utilizando el patrón asíncrono estándar basado en eventos para indicar la finalización en el contexto de sincronización original, exactamente de la misma manera que lo hacen los métodos XxxAsync en .NET Framework (por ejemplo,DownloadFileAsync
). Decido usar la Biblioteca de tareas paralelas para la implementación porque es muy fácil implementar este comportamiento con el siguiente código:
public class MyLibrary {
public event EventHandler SomeOperationCompleted;
private void OnSomeOperationCompleted() {
var handler = SomeOperationCompleted;
if (handler != null)
handler(this, EventArgs.Empty);
}
public void DoSomeOperationAsync() {
Task.Factory
.StartNew
(
() => Thread.Sleep(1000) // simulate a long operation
, CancellationToken.None
, TaskCreationOptions.None
, TaskScheduler.Default
)
.ContinueWith
(t => OnSomeOperationCompleted()
, TaskScheduler.FromCurrentSynchronizationContext()
);
}
}
Hasta ahora, todo funciona bien. Ahora, hagamos una llamada a esta biblioteca con un clic de botón en una aplicación WPF o WinForms:
private void Button_OnClick(object sender, EventArgs args) {
var myLibrary = new MyLibrary();
myLibrary.SomeOperationCompleted += (s, e) => DoSomethingElse();
myLibrary.DoSomeOperationAsync();
}
private void DoSomethingElse() {
...
Task.Factory.StartNew(() => Thread.Sleep(5000)/*simulate a long operation*/);
...
}
quí, la persona que escribió la llamada a la biblioteca decidió comenzar una nuevaTask
cuando se completa la operación. Nada inusual. Él o ella siguen ejemplos que se encuentran en todas partes en la web y simplemente usanTask.Factory.StartNew
sin especificar elTaskScheduler
(y no hay sobrecarga fácil para especificarlo en el segundo parámetro). LosDoSomethingElse
l método @ funciona bien cuando se llama solo, pero tan pronto como es invocado por el evento, la IU se congela desdeTaskFactory.Current
reutilizará el programador de tareas de contexto de sincronización desde la continuación de mi biblioteca.
Descubrir esto podría llevar algún tiempo, especialmente si la segunda llamada de tarea está enterrada en una pila de llamadas compleja. Por supuesto, la solución aquí es simple una vez que sepa cómo funciona todo: siempre especifiqueTaskScheduler.Default
para cualquier operación que espere ejecutar en el grupo de subprocesos. Sin embargo, tal vez la segunda tarea sea iniciada por otra biblioteca externa, sin conocer este comportamiento y usando ingenuamenteStartNew
sin un planificador específico. Espero que este caso sea bastante común.
Después de comprenderlo, no puedo entender la elección del equipo que escribe el TPL para usarTaskScheduler.Current
en lugar deTaskScheduler.Default
por defecto:
Default
@ no es el predeterminado! Y la documentación es muy deficiente. El programador de tareas real utilizado porCurrent
depende de la pila de llamadas! Es difícil mantener invariantes con este comportamiento. Es engorroso especificar el programador de tareas conStartNew
ya que primero debe especificar las opciones de creación de tareas y el token de cancelación, lo que genera líneas largas y menos legibles. Esto se puede aliviar escribiendo un método de extensión o creando unTaskFactory
que usaDefault
.a captura de la pila de llamadas tiene costos de rendimiento adicionale Cuando realmente quiero que una tarea dependa de otra tarea principal en ejecución, prefiero especificarla explícitamente para facilitar la lectura del código en lugar de confiar en la magia de la pila de llamadas.Sé que esta pregunta puede sonar bastante subjetiva, pero no puedo encontrar un buen argumento objetivo de por qué este comportamiento es así. Estoy seguro de que me falta algo aquí: por eso me estoy volviendo hacia ti.