2012-01-30 16 views
12

Así que estoy tratando de entender esta nueva 'asincronización' en .NET 4.5. Me previamente jugado un poco con los controladores asincrónicos y la Biblioteca paralelo de tareas y terminó con este pedazo de código:MVC4 asincrónico y ejecución paralela

Tome este modelo:

public class TestOutput 
{ 
    public string One { get; set; } 
    public string Two { get; set; } 
    public string Three { get; set; } 

    public static string DoWork(string input) 
    { 
     Thread.Sleep(2000); 
     return input; 
    } 
} 

que se utiliza en un controlador de la siguiente manera:

public void IndexAsync() 
{ 
    AsyncManager.OutstandingOperations.Increment(3); 

    Task.Factory.StartNew(() => 
     { 
      return TestOutput.DoWork("1"); 
     }) 
     .ContinueWith(t => 
     { 
      AsyncManager.OutstandingOperations.Decrement(); 
      AsyncManager.Parameters["one"] = t.Result; 
     }); 
    Task.Factory.StartNew(() => 
     { 
      return TestOutput.DoWork("2"); 
     }) 
     .ContinueWith(t => 
     { 
      AsyncManager.OutstandingOperations.Decrement(); 
      AsyncManager.Parameters["two"] = t.Result; 
     }); 
    Task.Factory.StartNew(() => 
     { 
      return TestOutput.DoWork("3"); 
     }) 
     .ContinueWith(t => 
     { 
      AsyncManager.OutstandingOperations.Decrement(); 
      AsyncManager.Parameters["three"] = t.Result; 
     }); 
} 

public ActionResult IndexCompleted(string one, string two, string three) 
{ 
    return View(new TestOutput { One = one, Two = two, Three = three }); 
} 

Este controlador muestra la vista en 2 segundos, gracias a la magia del TPL.

Ahora que esperaba (ingenuamente) que el código anterior se traduciría en la siguiente, utilizando el nuevo 'asíncrono' y 'esperar' características de C# 5:

public async Task<ActionResult> Index() 
{ 
    return View(new TestOutput 
    { 
     One = await Task.Run(() =>TestOutput.DoWork("one")), 
     Two = await Task.Run(() =>TestOutput.DoWork("two")), 
     Three = await Task.Run(() =>TestOutput.DoWork("three")) 
    }); 
} 

Este controlador representa la vista en 6 segundos. En algún lugar de la traducción, el código dejó de ser paralelo. Sé que async y paralelo son dos conceptos diferentes, pero de alguna manera pensé que el código funcionaría de la misma manera. ¿Podría alguien señalar qué está sucediendo aquí y cómo se puede solucionar?

+0

Así que básicamente mezclé la asincronía y el paralelismo. I (erróneamente) pensé que la espera real ocurriría cuando el resultado de una tarea se utilizara finalmente (no muy diferente de como una consulta linq solo se ejecuta cuando se enumera). – Lodewijk

Respuesta

18

En algún lugar de la traducción, el código dejó de ser paralelo.

Precisamente. await esperará (asincrónicamente) que se complete una sola operación.

operaciones asíncronas en paralelo se puede hacer iniciando las reales Task s pero no await ing ellos hasta más tarde:

public async Task<ActionResult> Index() 
{ 
    // Start all three operations. 
    var tasks = new[] 
    { 
    Task.Run(() =>TestOutput.DoWork("one")), 
    Task.Run(() =>TestOutput.DoWork("two")), 
    Task.Run(() =>TestOutput.DoWork("three")) 
    }; 

    // Asynchronously wait for them all to complete. 
    var results = await Task.WhenAll(tasks); 

    // Retrieve the results. 
    return View(new TestOutput 
    { 
    One = results[0], 
    Two = results[1], 
    Three = results[2] 
    }); 
} 

P. S. También hay un Task.WhenAny.

+0

De acuerdo, entiendo la diferencia entre asincrónico y paralelo (yo lo adivino al hacer la pregunta), pero ¿es necesario 'esperar' los resultados de la tarea? ¿Cuándo todo? Si la acción en el controlador ya es 'async', esperará dentro del método asincrónico no crear una carga adicional en el sistema? – Lodewijk

+0

Sí, es necesario, porque necesita completar las tareas antes de construir la vista. El uso de espera asincrónica es una carga menor que el bloqueo sincrónico: es más o menos lo mismo que el antiguo enfoque 'AsyncManager'. –

+0

Un punto importante: la palabra clave 'async' no hace * nada * excepto para permitir la palabra clave' await' en ese método. Es la palabra clave 'await', no la palabra clave' async', lo que hace que las cosas sean asincrónicas. Entonces, realmente debería tener un 'await' en algún lugar de cada método' async' (el compilador le avisará si no lo hace). –

5

No, usted indicó el motivo de que esto ya es diferente. Parallel y Async son dos cosas diferentes.

La versión de Tarea funciona en 2 segundos porque ejecuta las tres operaciones al mismo tiempo (siempre que tenga 3 o más procesadores).

La espera es en realidad lo que parece, el código esperará la ejecución de la tarea. Ejecutar antes de continuar con la siguiente línea de código.

Por lo tanto, la gran diferencia entre la versión TPL y la versión asincrónica es que la versión TPL se ejecuta en cualquier orden porque todas las tareas son independientes entre sí. Mientras que, la versión asíncrona se ejecuta en el orden en que se escribe el código. Entonces, si quiere paralelo, use el TPL, y si quiere asincrónico, use asincrónico.

El objetivo de la función asíncrona es la capacidad de escribir código de aspecto síncrono que no bloqueará una interfaz de usuario mientras se está ejecutando una acción larga. Sin embargo, esto es típicamente una acción que todo el procesador está haciendo esperando una respuesta. El async/await lo hace para que el código que llamó al método async no espere que regrese el método async, eso es todo.Por lo tanto, si realmente quería emular su primer modelo utilizando asíncrono/espera (que yo NO sugieren), se podría hacer algo como esto:

MainMethod() 
{ 
    RunTask1(); 
    RunTask2(); 
    RunTask3(); 
} 

async RunTask1() 
{ 
    var one = await Task.Factory.StartNew(()=>TestOutput.DoWork("one")); 
    //do stuff with one 
} 

async RunTask2() 
{ 
    var two= await Task.Factory.StartNew(()=>TestOutput.DoWork("two")); 
    //do stuff with two 
} 

async RunTask3() 
{ 
    var three= await Task.Factory.StartNew(()=>TestOutput.DoWork("three")); 
    //do stuff with three 
} 

La ruta de código va a ser algo como esto (si el tareas son de larga data)

  1. llamada principal para RunTask1
  2. RunTask1 espera y devuelve
  3. llamada principal para RunTask2
  4. RunTa sk2 espera y devuelve
  5. llamada principal para RunTask3
  6. RunTask3 espera y devuelve
  7. principales se hace ahora
  8. RunTask1/2/3 vuelve y sigue haciendo algo con uno/dos/tres
  9. Igual que 7 , a excepción de menos del uno que ya ha completado
  10. Igual 7, con la excepción de menos los dos que ya han completado

****Un gran descargo de responsabilidad acerca de esto, sin embargo. Await se ejecutará sincrónicamente si la tarea ya está completada para el momento en que se aguarda la espera. Esto evita que el tiempo de ejecución tenga que ejecutar su vudu :) ya que no es necesario. Esto hará que el flujo de código incorrecto, ya que el flujo es ahora sincrónica * ** *

el blog de Eric Lippert en esto explica las cosas mucho mejor de lo que yo estoy haciendo :) http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx

Afortunadamente, ¿eso ayuda a disipar algunas de sus preguntas sobre async versus TPL? Lo más importante es que async NO es paralelo.

+1

Me gustó su respuesta, pero acepté la otra porque contenía una versión viable de mi muestra. – Lodewijk