2012-01-15 13 views
6

Realmente no entiendo por qué await y async no mejoran el rendimiento de mi código aquí like they're supposed to.¿Por qué el async CTP funciona mal?

Aunque escéptico, pensé que se suponía que el compilador reescribiera mi método para que las descargas se hicieran en paralelo ... pero parece que en realidad no está sucediendo.
(me hago dan cuenta de que await y async no crean hilos separados;? Sin embargo, el sistema operativo debe estar haciendo las descargas en parallal, y volver a llamar mi código en el hilo original - ¿no debería)

¿Estoy usando async y await incorrectamente? ¿Cuál es la forma correcta de usarlos?

Código:

using System; 
using System.Net; 
using System.Threading; 
using System.Threading.Tasks; 

static class Program 
{ 
    static int SumPageSizesSync(string[] uris) 
    { 
     int total = 0; 
     var wc = new WebClient(); 
     foreach (var uri in uris) 
     { 
      total += wc.DownloadData(uri).Length; 
      Console.WriteLine("Received synchronized data..."); 
     } 
     return total; 
    } 

    static async Task<int> SumPageSizesAsync(string[] uris) 
    { 
     int total = 0; 
     var wc = new WebClient(); 
     foreach (var uri in uris) 
     { 
      var data = await wc.DownloadDataTaskAsync(uri); 
      Console.WriteLine("Received async'd CTP data..."); 
      total += data.Length; 
     } 
     return total; 
    } 

    static int SumPageSizesManual(string[] uris) 
    { 
     int total = 0; 
     int remaining = 0; 
     foreach (var uri in uris) 
     { 
      Interlocked.Increment(ref remaining); 
      var wc = new WebClient(); 
      wc.DownloadDataCompleted += (s, e) => 
      { 
       Console.WriteLine("Received manually async data..."); 
       Interlocked.Add(ref total, e.Result.Length); 
       Interlocked.Decrement(ref remaining); 
      }; 
      wc.DownloadDataAsync(new Uri(uri)); 
     } 
     while (remaining > 0) { Thread.Sleep(25); } 
     return total; 
    } 

    static void Main(string[] args) 
    { 
     var uris = new string[] 
     { 
      // Just found a slow site, to demonstrate the problem :) 
      "http://www.europeanchamber.com.cn/view/home", 
      "http://www.europeanchamber.com.cn/view/home", 
      "http://www.europeanchamber.com.cn/view/home", 
      "http://www.europeanchamber.com.cn/view/home", 
      "http://www.europeanchamber.com.cn/view/home", 
     }; 
     { 
      var start = Environment.TickCount; 
      SumPageSizesSync(uris); 
      Console.WriteLine("Synchronous: {0} milliseconds", Environment.TickCount - start); 
     } 
     { 
      var start = Environment.TickCount; 
      SumPageSizesManual(uris); 
      Console.WriteLine("Manual: {0} milliseconds", Environment.TickCount - start); 
     } 
     { 
      var start = Environment.TickCount; 
      SumPageSizesAsync(uris).Wait(); 
      Console.WriteLine("Async CTP: {0} milliseconds", Environment.TickCount - start); 
     } 
    } 
} 

Salida:

Received synchronized data... 
Received synchronized data... 
Received synchronized data... 
Received synchronized data... 
Received synchronized data... 
Synchronous: 14336 milliseconds 
Received manually async data... 
Received manually async data... 
Received manually async data... 
Received manually async data... 
Received manually async data... 
Manual: 8627 milliseconds   // Almost twice as fast... 
Received async'd CTP data... 
Received async'd CTP data... 
Received async'd CTP data... 
Received async'd CTP data... 
Received async'd CTP data... 
Async CTP: 13073 milliseconds  // Why so slow?? 

Respuesta

10

La respuesta de Chris es casi correcta, pero introduce una condición de carrera y bloques sincrónicos en todas las tareas.

Como regla general, es mejor no usar la continuación de tareas si tiene disponible await/async. Además, no use WaitAny/WaitAll - los equivalentes async son WhenAny y WhenAll.

lo escribiría así:

static async Task<int> SumPageSizesAsync(IEnumerable<string> uris) 
{ 
    // Start one Task<byte[]> for each download. 
    var tasks = uris.Select(uri => new WebClient().DownloadDataTaskAsync(uri)); 

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

    // Calculate the sum. 
    return results.Sum(result => result.Length); 
} 
+0

Oh, eh, esto se ve mucho más limpio. =) Gracias! – Mehrdad

1

puedo ser mala lectura de su código, pero parece que va a iniciar un subproceso de fondo para hacer la lectura asíncrona y luego el bloqueo inmediato, a la espera para que se complete Nada sobre la parte 'asincrónica' de su código es realmente asincrónico. Prueba esto:

static async Task<int> SumPageSizesAsync(string[] uris) 
{ 
    int total = 0; 
    var wc = new WebClient(); 
    var tasks = new List<Task<byte[]>>(); 
    foreach (var uri in uris) 
    { 
     tasks 
      .Add(wc.DownloadDataTaskAsync(uri).ContinueWith(() => { total += data.Length; 
     })); 
    } 
    Task.WaitAll(tasks); 
    return total; 
} 

y utilizarlo así:

{ 
     var start = Environment.TickCount; 
     await SumPageSizesAsync(uris); 
     Console.WriteLine("Async CTP: {0} milliseconds", Environment.TickCount - start); 
    } 

que podría ser mal; el material asíncrono es nuevo y no estoy 100% familiar con IT- pero el momento similar a la sincronización la versión parece confirmarme.

+0

Hm ... así que supongo que la pregunta lógica entonces es, ¿cuál es la correcta * * manera de hacerlo? – Mehrdad

+0

Editado para agregar cómo creo que debería funcionar. Nuevamente, podría estar totalmente equivocado. –

+0

Gracias! Aunque pensé que todo el punto de 'esperar' era cambiar el resto del método en estilo de paso de continuación (y el objetivo de la CTP asincrónica es eliminar la necesidad de tanta plomería con delegados/lambdas) ... entonces, ¿cómo? es esto diferente de, por ejemplo, 'BeginInvoke' y whatnot, que ya teníamos desde al menos .NET 2.0? – Mehrdad

Cuestiones relacionadas