2012-03-27 9 views
57

Dos nuevas palabras clave agregadas al lenguaje C# 5.0 son async y await, ambas funcionan mano a mano para ejecutar un método C# de forma asincrónica sin bloquear el hilo de llamada.¿Las nuevas palabras clave '# async' y 'await' de C# 5.0 usan múltiples núcleos?

Mi pregunta es, ¿estos métodos realmente aprovechan los múltiples núcleos y se ejecutan en paralelo o el método asíncrono se ejecuta en el mismo núcleo del subproceso que la persona que llama?

+5

Depende del objeto awaitable devuelto por el método async. – phoog

+1

Ninguno. Son solo azúcar sintáctico. Enhebrar depende del 'SynchronizationContext' actual y de la 'Tarea' que esperas. – CodesInChaos

Respuesta

93

Dos nuevas palabras clave agregadas al lenguaje C# 5.0 son asincrónicas y esperan, ambas funcionan mano a mano para ejecutar un método C# de forma asincrónica sin bloquear el hilo de llamada.

que obtiene a través de la propósito de la función, pero le da demasiada "crédito" a la función esperan ser asíncrono /.

Permítanme ser muy, muy claro en este punto: await no hace mágicamente que un método síncrono se ejecute de forma asíncrona. No inicia un nuevo hilo y ejecuta el método en el nuevo hilo, por ejemplo. El método que está llamando tiene que ser lo que sepa cómo ejecutarse de manera asíncrona. Cómo elige hacerlo es su negocio.

Mi pregunta es, ¿estos métodos realmente aprovechan múltiples núcleos y se ejecutan en paralelo o el método asíncrono se ejecuta en el mismo núcleo del subproceso que la persona que llama?

Una vez más, eso es por completo según el método que llame al. Todo lo que await hace es ordenar al compilador que reescriba el método en un delegado que se puede pasar como la continuación de la tarea asincrónica. Es decir, await FooAsync() significa "llamar al FooAsync() y todo lo que regrese debe ser algo que represente la operación asíncrona que acaba de iniciar. Dígale a la cosa que cuando sepa que la operación asíncrona está hecha, debería llamar a este delegado". El delegado tiene la propiedad de que cuando se invoca, el método actual parece reanudarse "donde lo dejó".

Si el método que llama cronogramas trabaja en otro hilo afinitizado a otro núcleo, genial. Si inicia un temporizador que hace ping a algún controlador de eventos en el futuro en el hilo de UI, genial. await no le importa. Todo lo que hace es asegurarse de que cuando se realiza el trabajo asincrónico, el control puede reanudarse donde lo dejó.

Una pregunta que no preguntar, pero probablemente debería tener es:

Cuando la tarea asíncrona está terminado y el control continúa donde lo dejó, es la ejecución en el mismo hilo como lo era antes?

Depende del contexto. En una aplicación de winforms en la que espera algo de la secuencia de comandos de la interfaz de usuario, el control vuelve a aparecer en el hilo de la interfaz de usuario. En una aplicación de consola, tal vez no.

+1

Me gusta su explicación aquí y realmente me ayuda a comprender mejor la sincronización y esperar. – Icemanind

+1

Si espera algo en el hilo de la interfaz de usuario, el control * normalmente * retoma el hilo de la interfaz de usuario, pero no tiene por qué hacerlo. Eso también depende del "algo que representa la operación asincrónica". Por ejemplo, sucede cuando esperas 'someTask.ConfigureAwait (false)'. – svick

2

Dado que async y await se basan en el TPL, deberían funcionar de manera muy similar. De forma predeterminada, debe tratarlos como si se ejecutaran en un hilo separado.

4

Un método async devuelve un objeto esperable (uno que tiene un método GetAwaiter), y el compilador puede generar código para consumir ese objeto si llama al método con la palabra clave await. También puede llamar a dicho método sin la palabra clave await y consumir el objeto explícitamente.

El objeto encapsula una acción asíncrona, que puede o no ejecutarse en otra secuencia. El artículo de Eric Lippert Asynchrony in C# 5.0 part Four: It's not magic considera un ejemplo de programación asincrónica que involucra solo un hilo.

65

Eric Lippert tiene una excelente respuesta; Solo quería describir el paralelismo async un poco más.

El enfoque simple "serie" es donde se await sólo una cosa a la vez:

static void Process() 
{ 
    Thread.Sleep(100); // Do CPU work. 
} 

static async Task Test() 
{ 
    await Task.Run(Process); 
    await Task.Run(Process); 
} 

En este ejemplo, el método Test pondrá en cola Process a la agrupación de hebras, y cuando se complete, se queue Process de nuevo al grupo de subprocesos. El método Test se completará después de ~ 200 ms. En cualquier momento, solo un hilo está realmente moviendo el progreso hacia adelante.

Una manera simple de paralelizar este es utilizar Task.WhenAll:

static void Process() 
{ 
    Thread.Sleep(100); // Do CPU work. 
} 

static async Task Test() 
{ 
    // Start two background operations. 
    Task task1 = Task.Run(Process); 
    Task task2 = Task.Run(Process); 

    // Wait for them both to complete. 
    await Task.WhenAll(task1, task2); 
} 

En este ejemplo, los Test colas método Process a la piscina hilo dos veces, y espera a que los dos para completar. El método Test se completará después de ~ 100 ms.

Task.WhenAll (y Task.WhenAny) se introdujeron con async/await para admitir el paralelismo simple. Sin embargo, el TPL todavía está allí si necesita algo más avanzado (el verdadero procesamiento en paralelo con CPU es mejor para el TPL). TPL funciona bien con async/await.

Cubro el paralelismo básico async en mi into to async blog post, así como el "contexto" al que Eric aludió.

+1

¡Gracias por esta respuesta también! – Icemanind

+0

Stephen, mencionas que 'TPL funciona bien con async/await'. ¿Podría señalarme alguna buena información sobre este tema específico? – Patrick

+0

@Patrick: si necesita hacer una asincronía de paralelismo * y *, consulte TPL Dataflow. –

Cuestiones relacionadas