Windows窗体应用程序中的任务并行库代码冻结 - 作为Windows控制台应用程序正常工作

时间:2012-11-15 21:54:29

标签: c# winforms task-parallel-library .net-4.5 ping

这个问题是我之前提出的问题的后续问题:

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

我能够获得接受的答案(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;
        }

    }

}

有没有人对它冻结的原因有任何想法?

1 个答案:

答案 0 :(得分:16)

因为WaitAll等待所有任务而且你在UI线程中,所以它阻塞了UI线程。阻止UI线程会冻结您的应用程序。

您想要做的事情,因为您使用的是C#5.0,而不是await Task.WhenAll(...)。 (您还需要在其定义中将该事件处理程序标记为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评论中基于this suggestion的另一个选项。

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选项更漂亮了,这也是一个相当简单的用例。