2012-11-15 16 views
5

To pytanie jest kontynuacją poprzedniego pytania, które ja zapytałem:Task Parallel Library Kod zamarza w Windows Forms Application - Działa dobrze jako aplikacji konsoli systemu Windows

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

Mogłem uzyskać akceptowaną odpowiedź (aplikację konsoli systemu Windows) do działania, ale gdy próbowałem uruchomić kod w aplikacji formularzy systemu Windows, poniższy kod zostanie zablokowany w wierszu zawierającym Task.WaitAll(pingTasks.ToArray()). Oto kod, który próbuję uruchomić:

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; 
     } 

    } 

} 

Czy ktoś ma jakieś pomysły, dlaczego jest mróz?

Odpowiedz

16

Jest mróz, ponieważ WaitAll czeka na wszystkie zadania i jesteś w wątku UI, więc to blokuje wątek interfejsu użytkownika. Zablokowanie wątku interfejsu użytkownika powoduje zatrzymanie aplikacji.

Zamiast tego, co chcesz zrobić, ponieważ jesteś w C# 5.0, to await Task.WhenAll(...). (Musisz także oznaczyć tę obsługę zdarzenia jako async w swojej definicji.) Nie będziesz musiał zmieniać żadnych innych aspektów kodu. To zadziała dobrze.

await nie będzie "czekać" w zadaniach. To, co zrobi, to, kiedy poczeka na oczekujące połączenie, połączy kontynuację z zadaniem, w którym się znajdujesz, i w tej kontynuacji uruchomi pozostałą część metody. Następnie, po okablowaniu tej kontynuacji, zakończy metodę i wróci do osoby dzwoniącej. Oznacza to, że wątek interfejsu użytkownika nie jest blokowany, ponieważ to zdarzenie kliknięcia zakończy się natychmiast.

(Na życzenie) Jeśli chcesz rozwiązać ten problem za pomocą C# 4.0, musimy zacząć od napisaniaod podstaw, ponieważ została dodana w wersji 5.0. Oto, co właśnie zbiłem. Prawdopodobnie nie jest tak wydajna jak implementacja biblioteki, ale powinna działać.

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; 
} 

Oto kolejna opcja na podstawie this suggestion w komentarzach przez svick.

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

Teraz, gdy mamy WhenAll po prostu trzeba użyć, że jak kontynuacje, zamiast await. Zamiast WaitAll będziesz używać:

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()); 

Teraz widać dlaczego opcja 5.0 jest ładniejsza, a to jest dość prosty przypadek użycia zbyt.

+1

Tak! Musiałem użyć 'await Task.WhenAll()' zamiast 'Task.WaitAll()' ... Musiałem również dodać 'async' do zdarzenia button_click. Dam ci szansę na odpowiedź na to pytanie. Dzięki! – HydroPowerDeveloper

+0

W celu uzyskania kompletności, czy możemy uzyskać alternatywne rozwiązanie przy korzystaniu z C# przed 5.0? – Pete

+0

I druga prośba Pete'a ... Ja też chciałbym poznać rozwiązanie <5.0. – HydroPowerDeveloper

Powiązane problemy