2012-01-06 14 views
11

Actualmente estoy trabajando en una aplicación principalmente asincrónica que utiliza TAP en todo momento. Cada clase que tiene métodos para generar Task s también tiene un TaskScheduler inyectado en él. Esto nos permite realizar una programación explícita de tareas, que según tengo entendido, no es la forma en que Microsoft utiliza el CTP Async.Async CTP: enfoque recomendado para la programación de tareas

El único problema que tengo con el nuevo enfoque (programación implícita) es que nuestra filosofía anterior siempre ha sido "sabemos que la continuación siempre especificará su programador de tareas, por lo que no tenemos que preocuparnos sobre qué contexto completamos la tarea en ".

Alejarse de eso nos preocupa un poco solo porque ha funcionado muy bien en términos de evitar errores sutiles de subprocesamiento, porque por cada bit de código podemos ver que el codificador se ha acordado de considerar en qué hilo está. Si se olvidaron de especificar el programador de tareas, es un error.

Pregunta 1: ¿Alguien me puede asegurar que el enfoque implícito es una buena idea? Veo tantos problemas que presenta ConfigureAwait (falso) y la programación explícita en el código heredado/de terceros. ¿Cómo puedo estar seguro de que mi código 'aguardado' siempre se ejecuta en el hilo de la interfaz de usuario, por ejemplo?

Pregunta 2: Así, suponiendo que quita todos los TaskScheduler DI de nuestro código y comenzar a utilizar la programación implícita, ¿cómo después fijar el programador de tareas por defecto? ¿Qué hay de cambiar el programador a medio camino a través de un método, justo antes de esperar un método costoso, y luego volver a configurarlo de nuevo después?

(P. S. Ya he leído http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx)

Respuesta

10

voy a tomar un tiro en responder. ;)

Pregunta 1: ¿Alguien me puede asegurar que el enfoque implícito es una buena idea? Veo tantos problemas que presenta ConfigureAwait (falso) y la programación explícita en el código heredado/de terceros. ¿Cómo puedo estar seguro de que mi código 'aguardado' siempre se ejecuta en el hilo de la interfaz de usuario, por ejemplo?

Las reglas para ConfigureAwait(false) son bastante simples: usarlo si el resto de su método se puede ejecutar en el subprocesos, y no lo use si el resto de su método debe funcionar en un contexto determinado (por ejemplo, Contexto de la interfaz de usuario).

En general, ConfigureAwait(false) debe utilizarse por código de biblioteca y no por código de capa UI (incluidas las capas tipo UI como ViewModels en MVVM). Si el método es parcialmente-computación de fondo y parcialmente-actualizaciones de UI, entonces debe dividirse en dos métodos.

Pregunta 2: Por lo tanto, suponiendo que quita todos los DI TaskScheduler de nuestro código y comenzar a utilizar la programación implícita, ¿cómo después fijar el programador de tareas por defecto?

async/await normalmente no utiliza TaskScheduler; usan un concepto de "contexto de programación". Esto es en realidad SynchronizationContext.Current, y vuelve a TaskScheduler.Current solo si no hay SynchronizationContext. La sustitución de su propio planificador se puede hacer usando SynchronizationContext.SetSynchronizationContext. Puede leer más sobre SynchronizationContext en this MSDN article on the subject.

El contexto de programación predeterminado debe ser lo que necesita casi todo el tiempo, lo que significa que no necesita meterse con él. Solo lo cambio cuando hago pruebas unitarias, o para programas de consola/servicios Win32.

¿Qué hay de cambiar el programador a medio camino a través de un método, justo antes de esperar un método costoso, y luego configurarlo de nuevo después?

Si usted quiere hacer una operación costosa (presumiblemente en el threadpool), entonces el resultado de awaitTaskEx.Run.

Si desea cambiar el planificador por otros motivos (por ejemplo, concurrencia), entonces await el resultado de TaskFactory.StartNew.

En ambos casos, el método (o delegado) se ejecuta en el otro planificador, y luego el resto del método se reanuda en su contexto habitual.

Lo ideal es que cada async método de existir dentro de un mismo contexto de ejecución. Si hay diferentes partes del método que necesitan contextos diferentes, divídalos en diferentes métodos. La única excepción a esta regla es ConfigureAwait(false), que permite que un método se inicie en un contexto arbitrario y luego vuelve al contexto de subprocesos durante el resto de su ejecución. ConfigureAwait(false) se debe considerar una optimización (está activada por defecto para el código de la biblioteca), no como una filosofía de diseño.

He aquí algunos puntos de mi "hilo está muerto" habla de que creo que puede ayudarle con su diseño:

  • Siga las directrices modelo asincrónico basado en tareas.
  • A medida que su base de código se vuelve más asincrónica, se volverá más funcional en naturaleza (en oposición a tradicionalmente orientada a objetos). Esto es normal y debería ser aceptado.
  • medida que su base de código se hace más asíncrono, la concurrencia de memoria compartida evoluciona gradualmente a la concurrencia de paso de mensajes (es decir, ConcurrentExclusiveSchedulerPair es el nuevo ReaderWriterLock).
+0

Gran respuesta Stephen, pero para aclarar: si ConfigureAwait (falso) fuera utilizado internamente por el método asíncrono 'A', ¿en qué contexto esperaría estar si esperaba el método 'A'? The threadpool, ¿o resumiría el contexto original antes de realizar una llamada esperada al método 'A'? –

+2

La respuesta de Stephen es bastante sólida. Tenga en cuenta que si está configurado como inactivo en su modelo anterior, siempre puede crear un envoltorio personalizado que se pueda esperar (de forma similar a cómo funciona ConfigureAwait()) y engancharlo como un método de extensión en Tarea/Tarea . Por ejemplo, si su método de extensión se llamó ResumeOn (TaskScheduler ts), entonces el código podría verse así: espera Foo (...). ResumeOn (ts); Y luego tienen la misma semántica de programación que su propio código, pero con todas las bondades mejoradas de flujo/ejecución que 'aguardan'. –

+2

@Lawrence: cada "capa" de métodos asíncronos pasa su contexto hacia abajo, pero no hacia arriba. Entonces, si 'A' llama' ConfigureAwait (false) ', terminará ejecutándose en el grupo de subprocesos. Entonces, cuando 'B' llama' await A() ', entonces' B' se reanudará en * su propio * contexto original después de 'await'. El hecho de que 'A' termine en el grupo de subprocesos no tiene efecto en el resto de' B'. –

Cuestiones relacionadas