Pregunta

No veo la diferencia entre C # 's (y de VB) nuevas características asincrónicos, y .NET 4.0 de tarea paralela Biblioteca. Tomemos, por ejemplo, el código de Eric Lippert de aquí :

async void ArchiveDocuments(List<Url> urls) {
    Task archive = null;
    for(int i = 0; i < urls.Count; ++i) {
        var document = await FetchAsync(urls[i]);
        if (archive != null)
            await archive;
        archive = ArchiveAsync(document);
    }
}

Parece que la palabra clave await cumple dos propósitos diferentes. La primera aparición (FetchAsync) parece significar, "Si se utiliza este valor más adelante en el método y su tarea no ha terminado, espere hasta que se complete antes de continuar." la segunda instancia (archive) parece significar, "Si esta tarea aún no ha terminado, espera ahora hasta que se complete." Si estoy equivocado, por favor, corríjanme .

¿No podría fácilmente ser escrito como este?

void ArchiveDocuments(List<Url> urls) {
    for(int i = 0; i < urls.Count; ++i) {
        var document = FetchAsync(urls[i]);       // removed await
        if (archive != null)
            archive.Wait();                       // changed to .Wait()
        archive = ArchiveAsync(document.Result);  // added .Result
    }
}

He reemplazado la primera await con un Task.Result cuando sea realmente necesaria el valor, y la segunda con await Task.Wait(), donde la espera está ocurriendo en realidad. La funcionalidad es (1) ya implementada, y mucho más cerca (2) semánticamente a lo que está sucediendo realmente en el código.

Me dan cuenta de que un método async se reescribe como una máquina de estados, de forma similar a los iteradores, pero también no ven lo que los beneficios que trae. Cualquier código que requiere otro hilo para operar (como la descarga) aún requerirá otro hilo, y cualquier código que no es así (como la lectura de un archivo) todavía podría utilizar el TPL de trabajo con un solo hilo.

Obviamente estoy perdiendo algo grande aquí; Puede cualquiera ayuda a entender esto un poco mejor?

¿Fue útil?

Solución

Creo que surge el malentendido aquí:

  

Parece que la palabra clave Await sirve para dos propósitos diferentes. La primera aparición (FetchAsync) parece que quiere decir: "Si se utiliza este valor en el método y su tarea no ha terminado, espere hasta que se complete antes de continuar." La segunda instancia (archivo) parece que quiere decir: "Si esta tarea aún no ha terminado, espera ahora hasta que se complete." Si estoy equivocado, por favor, corríjanme.

Esto es en realidad completamente incorrecta. Ambos tienen el mismo significado.

En el primer caso:

var document = await FetchAsync(urls[i]);

¿Qué ocurre aquí, es que el tiempo de ejecución dice "Inicio llamar FetchAsync, a continuación, volver al punto de ejecución actual al hilo de llamar a este método." No hay una "espera" aquí - en lugar, la ejecución vuelve al contexto de sincronización de llamadas, y las cosas seguir produciendo. En algún momento en el futuro, la tarea del FetchAsync completará, y en ese momento, este código se reanudará el contexto de sincronización del subproceso de llamada, y se producirá la siguiente instrucción (la asignación de la variable de documento).

Ejecución luego continuará hasta la segunda llamada esperan ser - y en ese momento, lo mismo va a pasar - si el Task<T> (archivo) no está completa, la ejecución se dará a conocer al contexto de llamada - de lo contrario, el archivo se establecerá .

En el segundo caso, las cosas son muy diferentes - aquí, estás bloqueando de forma explícita, lo que significa que el contexto de sincronización llamar nunca se tiene la oportunidad de ejecutar ningún código hasta que se complete método completo. Por supuesto, todavía hay asincronía, pero la asincronía está completamente contenida dentro de este bloque de código -. Ningún código fuera de este código pegado va a pasar en este hilo hasta que todas sus ultima código

Otros consejos

Hay una gran diferencia:

bloques Wait(), await no bloquea. Si ejecuta la versión asíncrona de ArchiveDocuments() en su hilo de interfaz gráfica de usuario, la interfaz gráfica permanecerá sensible, mientras que las operaciones de ir a buscar y archivar están ejecutando. Si se utiliza la versión TPL con Wait(), se bloqueará su interfaz gráfica de usuario.

Tenga en cuenta que async las arregla para hacer esto sin introducir ningún hilo - en el punto de la await, el control se devuelve al bucle de mensajes. Una vez que la tarea que se esperó a que se ha completado, el resto del método (continuación) es encolado en el bucle de mensaje y el hilo GUI continuará funcionando ArchiveDocuments donde lo dejó.

Anders hierve abajo a una respuesta muy sucinta en el Canal 9 en vivo entrevista que le hizo. Le recomiendo que

El nuevo asíncrono y palabras clave le espera, permiten a orquestar concurrencia en sus aplicaciones. Ellos en realidad no introducen ninguna concurrencia en su aplicación.

TPL y más específicamente de tareas es una manera que puede usar para realizar operaciones en realidad al mismo tiempo. La nueva palabra clave asíncrono y aguardan le permiten componer estas operaciones simultáneas en un "sincrónica" o "lineal" de la moda.

Así que todavía puede escribir un flujo lineal de control en sus programas de computación, mientras que el real puede o no puede ocurrir al mismo tiempo. Cuando el cálculo ocurre al mismo tiempo, esperan y asincrónico que permiten a componer estas operaciones.

La capacidad de convertir el flujo del programa del control en una máquina de estados es lo que hace que estas nuevas palabras clave intresting. Piense en ello como control de rendimiento , en lugar de valores.

este Canal 9 de vídeo de Anders hablando la nueva función.

El problema aquí es que la firma de ArchiveDocuments es engañoso. Tiene un rendimiento explícito de void pero realmente el retorno es Task. Para mí vacía implica sincrónica ya que no hay manera de "espera" a que termine. Considere la firma alternativa de la función.

async Task ArchiveDocuments(List<Url> urls) { 
  ...
}

Para mí, cuando está escrito de esta manera la diferencia es mucho más evidente. La función ArchiveDocuments no es uno que completa de forma sincrónica, pero terminará más tarde.

La llamada a FetchAsync() todavía bloqueará hasta que se complete (a menos que una declaración dentro de las llamadas await?) La clave es que el control se devuelve a la persona que llama (debido a que el método de ArchiveDocuments sí está declarada como async). Por lo que la persona que llama felizmente puede continuar procesando la lógica de interfaz de usuario, responder a eventos, etc.

Cuando se complete FetchAsync(), se interrumpe la persona que llama termine el bucle. Se golpea ArchiveAsync() y bloques, pero ArchiveAsync() probablemente sólo crea una nueva tarea, lo arranca, y devuelve la tarea. Esto permite que el segundo bucle para empezar, mientras que la tarea está procesando.

El segundo bucle golpea FetchAsync() y bloques, de devolver el control a la persona que llama. Cuando se complete FetchAsync(), de nuevo se interrumpe la persona que llama a continuar con el procesamiento. A continuación, golpea await archive, que devuelve el control a la persona que llama hasta que el Task creada en bucle 1 se complete. Una vez que la tarea se ha completado, la persona que llama se interrumpe de nuevo, y el segundo bucle llama ArchiveAsync(), que recibe una tarea iniciada y comienza el bucle 3, repita los hasta la saciedad .

La clave está devolviendo el control al llamador mientras que los levantadores pesados ??están ejecutando.

La palabra clave Await no introduce concurrencia. Es como la palabra clave de rendimiento, que le dice al compilador para reestructurar el código en lambda controlado por una máquina de estados.

Para ver qué código esperan ser vería sin 'esperan ser' ver este excelente enlace: http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await .aspx

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top