2012-11-15 15 views
5

Questa domanda è un follow-up a una domanda precedente che avevo chiesto:Task Parallel biblioteca di codice si blocca in un'applicazione Windows Form - Funziona bene come una console di Windows Application

How to Perform Multiple "Pings" in Parallel using C#

ero in grado di ottenere la risposta accettata (un'applicazione per console Windows) per funzionare, ma quando ho provato a eseguire il codice in un'applicazione Windows Form, il codice seguente verrà bloccato sulla riga contenente Task.WaitAll(pingTasks.ToArray()). Ecco il codice che sto provando a fare funzionare:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 
using System.Net.NetworkInformation; 

namespace WindowsFormsApplication1 
{ 
    public partial class Form1 : Form 
    { 
     public Form1() 
     { 
      InitializeComponent(); 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 

      List<String> addresses = new List<string>(); 

      for (Int32 i = 0; i < 10; ++i) addresses.Add("microsoft.com"); 

      List<Task<PingReply>> pingTasks = new List<Task<PingReply>>(); 
      foreach (var address in addresses) 
      { 
       pingTasks.Add(PingAsync(address)); 
      } 

      //Wait for all the tasks to complete 
      Task.WaitAll(pingTasks.ToArray()); 

      //Now you can iterate over your list of pingTasks 
      foreach (var pingTask in pingTasks) 
      { 
       //pingTask.Result is whatever type T was declared in PingAsync 
       textBox1.Text += Convert.ToString(pingTask.Result.RoundtripTime) + Environment.NewLine; 

      } 

     } 

     private Task<PingReply> PingAsync(string address) 
     { 
      var tcs = new TaskCompletionSource<PingReply>(); 
      Ping ping = new Ping(); 
      ping.PingCompleted += (obj, sender) => 
      { 
       tcs.SetResult(sender.Reply); 
      }; 
      ping.SendAsync(address, new object()); 
      return tcs.Task; 
     } 

    } 

} 

Qualcuno ha qualche idea sul perché si gela?

risposta

16

Si sta bloccando perché WaitAll attende su tutte le attività e ci si trova nel thread dell'interfaccia utente, quindi questo blocca il thread dell'interfaccia utente. Il blocco del thread dell'interfaccia utente blocca la tua applicazione.

Quello che vuoi fare, dato che sei in C# 5.0, è await Task.WhenAll(...) invece. (Devi anche contrassegnare il gestore di eventi come async nella sua definizione.) Non dovrai modificare altri aspetti del codice. Funzionerà bene.

await non "attende" effettivamente nelle attività. Quello che farà è che, quando arriva l'attesa, collegherà una continuazione al task su cui si è await (in questo caso, quando tutto) e in quella continuazione verrà eseguito il resto del metodo. Quindi, dopo aver completato questa continuazione, terminerà il metodo e tornerà al chiamante. Ciò significa che il thread dell'interfaccia utente non è bloccato, poiché questo evento clic termina immediatamente.

(Su richiesta) Se si desidera risolvere questo problema utilizzando C# 4.0, sarà necessario iniziare scrivendo WhenAll da zero, poiché è stato aggiunto in 5.0. Ecco cosa ho appena montato. Probabilmente non è abbastanza efficiente come l'implementazione della libreria, ma dovrebbe funzionare.

public static Task WhenAll(IEnumerable<Task> tasks) 
{ 
    var tcs = new TaskCompletionSource<object>(); 
    List<Task> taskList = tasks.ToList(); 

    int remainingTasks = taskList.Count; 

    foreach (Task t in taskList) 
    { 
     t.ContinueWith(_ => 
     { 
      if (t.IsCanceled) 
      { 
       tcs.TrySetCanceled(); 
      } 
      else if (t.IsFaulted) 
      { 
       tcs.TrySetException(t.Exception); 
      } 
      else //competed successfully 
      { 
       if (Interlocked.Decrement(ref remainingTasks) == 0) 
        tcs.TrySetResult(null); 
      } 
     }); 
    } 

    return tcs.Task; 
} 

Ecco un'altra opzione sulla base this suggestion nei commenti di svick.

public static Task WhenAll(IEnumerable<Task> tasks) 
{ 
    return Task.Factory.ContinueWhenAll(tasks.ToArray(), _ => { }); 
} 

Ora che abbiamo WhenAll Abbiamo solo bisogno di utilizzare tale, così come continuazioni, invece di await. Invece di WaitAll userete:

MyClass.WhenAll(pingTasks) 
    .ContinueWith(t => 
    { 
     foreach (var pingTask in pingTasks) 
     { 
      //pingTask.Result is whatever type T was declared in PingAsync 
      textBox1.Text += Convert.ToString(pingTask.Result.RoundtripTime) + Environment.NewLine; 
     } 
    }, CancellationToken.None, 
    TaskContinuationOptions.None, 
    //this is so that it runs in the UI thread, which we need 
    TaskScheduler.FromCurrentSynchronizationContext()); 

Ora si vede il motivo per cui l'opzione 5.0 è più bella, e questo è un ragionevolmente semplice caso d'uso anche.

+1

Sì !!! Ho dovuto usare 'attendere Task.WhenAll()' invece di 'Task.WaitAll()' ... Ho anche dovuto aggiungere un 'async' all'evento button_click. Ti darò il merito di rispondere a questa domanda. Grazie! – HydroPowerDeveloper

+0

Per completezza, possiamo ottenere la soluzione alternativa quando si utilizza C# prima della 5.0? – Pete

+0

In secondo luogo la richiesta di Pete ... Anch'io vorrei conoscere la <5.0 soluzione. – HydroPowerDeveloper