2012-04-23 18 views
50

Tratando de entender la diferencia entre TPL & async/await cuando se trata de la creación de subprocesos.Diferencia entre TPL y async/await (manejo de subprocesos)

Creo que el TPL (TaskFactory.Startnew) funciona de forma similar a ThreadPool.QueueUserWorkItem en que pone en cola el trabajo en un subproceso en el grupo de subprocesos. Eso es, por supuesto, a menos que use TaskCreationOptions.LongRunning, que crea un nuevo hilo.

pensé asíncrono/Await funcionaría de manera similar por lo que en esencia:

TPL:

Factory.StartNew(() => DoSomeAsyncWork()) 
.ContinueWith( 
    (antecedent) => { 
     DoSomeWorkAfter(); 
    },TaskScheduler.FromCurrentSynchronizationContext()); 

asíncrono/esperan:

await DoSomeAsyncWork(); 
DoSomeWorkAfter(); 

sería idéntica. De lo que he estado leyendo parece que async/await solo "a veces" crea un nuevo hilo. Entonces, ¿cuándo crea un nuevo hilo y cuándo no crea un nuevo hilo? Si tuviera que lidiar con puertos de terminación de IO, podría ver que no tiene que crear un nuevo hilo, pero de lo contrario, creo que debería hacerlo. Creo que mi comprensión de FromCurrentSynchronizationContext siempre fue un poco confusa también. Siempre pensé que era, en esencia, el hilo de UI.

+2

En realidad, TaskCreationOptions.LongRunning no garantiza un "nuevo hilo". Por MSDN, * la opción "LongRunning" solo proporciona una pista para el planificador; no garantiza un hilo dedicado. * Lo descubrí por el camino difícil. – eduncan911

+0

@ eduncan911 aunque lo que dices acerca de la documentación es correcto, busqué el código fuente de TPL hace un tiempo y estoy bastante seguro de que siempre se crea un nuevo subproceso dedicado cuando se especifica 'TaskCreationOptions.LongRunning'. –

+0

@ZaidMasud: Es posible que desee echar otro vistazo.Sé que estaba agrupando los hilos porque 'Thread.CurrentThread.IsThreadPoolThread' volvía a ser verdadero para los hilos de corta duración de unos pocos cientos de milisegundos. sin mencionar las variables ThreadStatic que estaba usando sangrado en múltiples hilos, causando todo tipo de havok. Tuve que forzar mi código para actualizar múltiples Thread() s, la forma de la vieja escuela, para garantizar un hilo dedicado. En otras palabras, no podría usar TaskFactory para hilos dedicados. Opcionalmente, podría implementar su propio 'TaskScheduler' que siempre devuelve un hilo dedicado. – eduncan911

Respuesta

64

creo que el TPL (TaskFactory.Startnew) funciona de forma similar a ThreadPool.QueueUserWorkItem porque i t colas de trabajo en un hilo en el grupo de subprocesos.

Pretty much.

De lo que he estado leyendo parece que async/await solo "a veces" crea un nuevo hilo.

En realidad, nunca lo hace. Si quieres multihilo, debes implementarlo tú mismo. Hay un nuevo método Task.Run que es solo una abreviatura de Task.Factory.StartNew, y es probablemente la forma más común de iniciar una tarea en el grupo de subprocesos.

Si estaba tratando con puertos de terminación de E/S, puedo ver que no es necesario crear un nuevo hilo, pero de lo contrario, creo que debería hacerlo.

Bingo. Entonces, los métodos como Stream.ReadAsync realmente crearán un contenedor Task alrededor de un IOCP (si el Stream tiene un IOCP).

También puede crear algunas "tareas" que no sean de E/S ni de CPU. Un ejemplo simple es Task.Delay, que devuelve una tarea que se completa después de un período de tiempo.

Lo bueno de async/await es que se puede programar de algo de trabajo para el grupo de subprocesos (por ejemplo, Task.Run), hacer alguna operación de E/S determinada (por ejemplo, Stream.ReadAsync), y hacer alguna otra operación (por ejemplo, Task.Delay) ... ¡y todas son tareas! Se pueden esperar o usar en combinaciones como Task.WhenAll.

Cualquier método que devuelva Task puede ser await ed - no tiene que ser un método async. Entonces, Task.Delay y las operaciones de E/S solo usan TaskCompletionSource para crear y completar una tarea: lo único que se hace en el grupo de subprocesos es completar la tarea real cuando ocurre el evento (tiempo de espera, finalización de E/S, etc.).

Creo que mi comprensión de FromCurrentSynchronizationContext siempre fue un poco confusa también. Siempre pensé que era, en esencia, el hilo de UI.

Escribí an article en SynchronizationContext. La mayoría de las veces, SynchronizationContext.Current:

  • es un contexto de IU si el hilo actual es un hilo de IU.
  • es un contexto de solicitud ASP.NET si el hilo actual está dando servicio a una solicitud ASP.NET.
  • es un contexto de grupo de subprocesos de lo contrario.

Cualquier hilo puede fijar sus propias SynchronizationContext, por lo que hay excepciones a las reglas anteriores.

Tenga en cuenta que el valor predeterminado awaiter Task programará el resto del método async de la corriente SynchronizationContextsi no es nulo; de lo contrario, continúa en el número actual TaskScheduler. Esto no es tan importante hoy, pero en el futuro cercano será una distinción importante.

Escribí mi propio async/await intro en mi blog, y Stephen Toub publicó recientemente un excelente async/await FAQ.

En relación con "concurrencia" vs "multiproceso", consulte this related SO question. Yo diría que async habilita la simultaneidad, que puede ser multiproceso o no. Es fácil de usar await Task.WhenAll o await Task.WhenAny para realizar un procesamiento simultáneo, y a menos que use explícitamente el grupo de subprocesos (por ejemplo, Task.Run o ConfigureAwait(false)), puede tener varias operaciones simultáneas en progreso al mismo tiempo (por ejemplo, múltiples E/S u otras tipos como Delay) - y no hay hilo necesario para ellos. Utilizo el término "simultaneidad de subproceso único" para este tipo de escenario, aunque en un host ASP.NET, en realidad se puede terminar con " -competencia en threads". Que es bastante dulce

+1

Buena respuesta. También recomendaría http://www.infoq.com/articles/Async-API-Design y esta excelente presentación: http://channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B318. – Philippe

+0

El primer enlace está muerto. –

+0

@FelipeDeveza ¡Reparado, gracias! –

8

asíncrono/espera básicamente simplifica los ContinueWith métodos (continuaciones en Continuation Passing Style)

No introducir concurrencia - que todavía tiene que hacerlo usted mismo (o utilizar la versión asíncrona de un método del marco.)

por lo tanto, la versión C# 5 sería:

await Task.Run(() => DoSomeAsyncWork()); 
DoSomeWorkAfter(); 
+0

entonces, ¿dónde está ejecutando DoSomeAsyncWork (versión asíncrona/de espera) en mi ejemplo anterior? Si se ejecuta en la interfaz de usuario, ¿cómo no se bloquea? – coding4fun

+1

Su ejemplo de espera no se compilará si 'DoSomeWorkAsync()' devuelve vacío o algo que no está a la espera. Desde su primer ejemplo, asumí que es un método secuencial que desea ejecutar en un hilo diferente. Si lo cambiaste para devolver una 'Tarea', sin introducir concurrencia, entonces sí se bloquearía. En el sentido de que se ejecutaría de forma secuencial y sería exactamente como el código normal en el hilo de la interfaz de usuario. 'await' solo cede si el método devuelve un awaitable que aún no se ha completado. –

+0

bueno, no diría que se ejecuta donde quiera que se ejecute. Has usado Task.Run para ejecutar el código en DoSomeAsyncWork, por lo que en este caso tu trabajo se realizará en una cadena de subprocesos. –

Cuestiones relacionadas