【问题标题】:Task Parallel Library Code Freezes in a Windows Forms Application - Works fine as a Windows Console ApplicationWindows 窗体应用程序中的任务并行库代码冻结 - 可作为 Windows 控制台应用程序正常工作
【发布时间】:2012-11-04 14:33:23
【问题描述】:

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

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

    }

}

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

【问题讨论】:

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


    【解决方案1】:

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

    由于您使用的是 C# 5.0,因此您想要做的是 await Task.WhenAll(...)。 (您还需要在其定义中将该事件处理程序标记为async。)您不需要更改代码的任何其他方面。这样就可以了。

    await 实际上不会在任务中“等待”。它会做的是,当它遇到等待时,它将继续连接到您正在执行的任务 awaiting(在这种情况下,当所有时),并且在该继续中它将运行该方法的其余部分。然后,在连接该延续之后,它将结束该方法并返回给调用者。这意味着 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 在 cmets 中的 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 选项更漂亮了,这也是一个相当简单的用例。

    【讨论】:

    • 是的!!!我必须使用await Task.WhenAll() 而不是Task.WaitAll()...我还必须在button_click 事件中添加async。我会给你回答这个问题的功劳。谢谢!
    • 为了完整起见,在 5.0 之前使用 C# 时,我们可以得到替代解决方案吗?
    • 我同意Pete的要求...我也想知道
    • @Pete C# 4.0 解决方案已添加。
    • 您的WhenAll() 版本不起作用,它更像WhenAny()。你有remainingTasks,但你不使用它,我认为这清楚地表明存在问题。此外,在 .Net 4.0 上实现 WhenAll() 的更简单方法是使用 TaskFactory.ContinueWhenAll(tasks, _ =&gt; {})
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-07
    • 1970-01-01
    • 1970-01-01
    • 2010-11-19
    • 1970-01-01
    • 2011-12-07
    相关资源
    最近更新 更多