任务并行库在Windows窗体应用程序中冻结 - 作为Windows控制台应用程序正常运行

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

    }

}

有人知道为什么会冻结吗?

1个回答

18
由于WaitAll等待所有任务完成,而您在UI线程中,所以会阻塞UI线程,从而导致应用程序冻结。
因为您在C# 5.0中,所以您需要做的是改用await Task.WhenAll(...)(您还需要在事件处理程序的定义中标记为async)。您不需要更改代码的任何其他方面。这样就可以正常工作了。 await实际上不会在任务中“等待”。当它遇到await时,它将为您正在等待的任务(在本例中是when all)连接一个继续项,并在该继续项中运行方法的其余部分。然后,在连接继续项之后,它将结束该方法并返回给调用者。这意味着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选项更漂亮了,而且这只是一个相当简单的用例。

1
是的!!!我不得不使用await Task.WhenAll()而不是Task.WaitAll()...我还必须在button_click事件中添加一个async。我会给你回答这个问题的功劳。谢谢! - HydroPowerDeveloper
为了完整起见,我们能否在使用 C# 5.0 之前得到替代解决方案? - Pete
我支持Pete的请求...我也想知道小于5.0的解决方案。 - HydroPowerDeveloper
1
@Pete 添加了C# 4.0解决方案。 - Servy
你的 WhenAll() 版本不会起作用,更像是 WhenAny()。你有 remainingTasks,但你没有使用它,我认为这是一个明显的指示存在问题。此外,在 .Net 4.0 上实现 WhenAll() 的更简单的方法是使用 TaskFactory.ContinueWhenAll(tasks, _ => {}) - svick
@svick 是的,我在我的版本中修复了错误,并添加了基于你的版本的版本。谢谢,我之前不熟悉 ContinueWhenAll - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接