6

この質問は、私が尋ねた前の質問のフォローアップです。

C#を使用して複数の「Ping」を並行して実行する方法

受け入れられた回答(Windowsコンソールアプリケーション)を機能させることができましたが、Windowsフォームアプリケーションでコードを実行しようとすると、次のコードが。を含む行でフリーズしますTask.WaitAll(pingTasks.ToArray())。これが私が実行しようとしているコードです:

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

    }

}

なぜそれが凍っているのかについて誰かが何か考えを持っていますか?

4

1 に答える 1

18

WaitAllすべてのタスクを待機し、UIスレッドを使用しているため、UIスレッドがブロックされているため、フリーズしています。UIスレッドをブロックすると、アプリケーションがフリーズします。

await Task.WhenAll(...)代わりに、C#5.0を使用しているので、やりたいことは次のとおりです。(そのイベントハンドラーをそのasync定義のようにマークする必要もあります。)コードの他の側面を変更する必要はありません。それはうまくいくでしょう。

awaitタスクで実際に「待機」することはありません。それが行うことは、それが待機に達すると、あなたが実行しているタスクawait(この場合はすべての場合)に継続を接続し、その継続でメソッドの残りの部分を実行することです。次に、その継続を配線した後、メソッドを終了して呼び出し元に戻ります。これは、このクリックイベントがすぐに終了するため、UIスレッドがブロックされないことを意味します。

(リクエストに応じて)C#4.0を使用してこれを解決したい場合はWhenAll、5.0で追加されたため、最初から作成する必要があります。これが私が今作ったものです。おそらくライブラリの実装ほど効率的ではありませんが、機能するはずです。

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

svickによるコメントのこの提案に基づく別のオプションがあります。

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

これでWhenAll、の代わりに継続だけでなく、それを使用する必要がありawaitます。代わりにWaitAll以下を使用します:

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

これで、5.0オプションがよりきれいである理由がわかります。これも、かなり単純なユースケースです。

于 2012-11-15T21:56:33.713 に答える