使用ContinueWith执行任务时为什么阻止Windows窗体UI?

时间:2018-02-08 08:51:33

标签: c# winforms task-parallel-library blocked continuewith

我花了几天时间在Google上搜索并试图理解为什么在我执行任务中执行ping时,Windows Forms 用户界面被阻止。 我看到了很多类似的案例,但没有一个案例能解释我的具体案例。

问题描述:

我有一个异步发送ping的应用程序。每个ping都在Task内部发送。我使用.ContinueWith来接收ping的结果并将其打印到文本框而不阻止UI线程。 如果我一次启动所有ping,它就可以正常工作。如果我添加while {run}循环使它们永远运行,我的UI就会变得无法响应并被阻止,并且没有任何结果会打印到文本框中。

有问题的代码:

Action action2 = () => {
        for (int i = 0; i < ipquantity; i++)
        {
            int temp1 = i;
            string ip = listView1.Items[temp1].SubItems[1].Text;

            if (finished[temp1] == true) // Variable helps to check if ping reply was received and printed
                continutask[temp1] = Task<string>.Run(() => PingStart(ip, temp1)).ContinueWith(antecedent => PrintResult(antecedent.Result, temp1));
        }
};

while (run)
{
    action2();
    Thread.Sleep(1000);
}

问题:

  1. 为什么UI被while循环阻止,为什么没有阻止它?
  2. 如何修改我的代码,以便仍然可以在不阻止UI的情况下将ping用于任务?
  3. 是否有更好的方法可以同时向多个IP地址发送无限次ping?
  4. 完整代码:

    private async void buttonStart_Click(object sender, EventArgs e)
    {
        run = true;
    
        int count = listView1.Items.Count;
        task = new Task<string>[count];
        result1 = new string[count];
        finished = new bool[count];
        continutask = new Task[count];
        for (int i = 0; i < count; i++)
        {
            finished[i] = true;
        }
    
            Action action2 = () =>
        {
            for (int i = 0; i < count; i++)
            {
                int temp1 = i;
                string ip = listView1.Items[temp1].SubItems[1].Text;
    
    
                if (finished[temp1] == true)
                    continutask[temp1] = Task<string>.Run(() => PingStart(ip, temp1)).ContinueWith(antecedent => PrintResult(antecedent.Result, temp1));
    
            }
        };
        while (run)
        {
            action2();
            //await Task.Delay;
            //Thread.Sleep(1000);
        }
    }
    
    public void PrintResult(string message, int seqnum)
    {
        Action action = () =>
        {
            textBox1.AppendText(message);
            textBox1.AppendText(Environment.NewLine);
            textBox1.AppendText("");
            textBox1.AppendText(Environment.NewLine);
        };
        if (InvokeRequired)
            Invoke(action);
        else
            action();
        finished[seqnum] = true;
    }
    
    public string PingStart(string ip, int seqnum)
    {
        finished[seqnum] = false;
    
        Ping isPing = new Ping();
        PingReply reply;
        const int timeout = 2000;
        const string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        var buffer = Encoding.ASCII.GetBytes(data);
        PingOptions options = new PingOptions();
        // Use the default Ttl value which is 128,
        options.DontFragment = false;
    
        reply = isPing.Send(ip, timeout, buffer, options);
        string rtt = (reply.RoundtripTime.ToString());
    
        string success = "N/A";
    
        if (reply.Status == IPStatus.Success)
        {
            success = $"{ip}" + " Success!" + $" rtt: [{rtt}]" + $"Thread: {Thread.CurrentThread.GetHashCode()} Is pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
        }
        else if (reply.Status != IPStatus.Success)
        {
            success = $"{ip}" + $" Not Successful! Status: {reply.Status}" + $"Thread: {Thread.CurrentThread.GetHashCode()} Is pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
        }
    
        return success;
    }
    

2 个答案:

答案 0 :(得分:1)

由于您已经创建(并保存)了您的任务,因此最简单的修复方法是在while循环的每次迭代中等待它们:

while (run)
{
    action2();

    foreach (Task t in continutask)
        await t;
}

这样,当所有ping完成(成功或不成功)时,你会再次开始整个过程​​ - 没有延迟。

还有一件事:您可以向textBox1.ScrollToEnd();

添加PrintResult

由于存在很大的改进空间,下面是一个重写和简化的例子。我删除了很多未使用的变量(例如seqnum)并使PingStart方法完全异步。我还使用TextBox替换了ListBox以便于测试,因此您可能希望在代码中恢复它。

这仍然不是所有可能实现的最干净的(主要是因为全局run)但它应该向您展示如何做事“more async”< / em>:)

private async void buttonStart_Click(object sender, EventArgs e)
{
    // If the ping loops are already running, don't start them again
    if (run)
        return;

    run = true;

    // Get all IPs (in my case from a TextBox instead of a ListBox
    string[] ips = txtIPs.Text.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);

    // Create an array to store all Tasks
    Task[] pingTasks = new Task[ips.Length];

    // Loop through all IPs
    for(int i = 0; i < ips.Length; i++)
    {
        string ip = ips[i];

        // Launch and store a task for each IP
        pingTasks[i] = Task.Run(async () =>
            {
                // while run is true, ping over and over again
                while (run)
                {
                    // Ping IP and wait for result (instead of storing it an a global array)
                    var result = await PingStart(ip);

                    // Print the result (here I removed seqnum)
                    PrintResult(result.Item2);

                    // This line is optional. 
                    // If you want to blast pings without delay, 
                    // you can remove it
                    await Task.Delay(1000);
                }
            }
        );
    }

    // Wait for all loops to end after setting run = false.
    // You could add a mechanism to call isPing.SendAsyncCancel() instead of waiting after setting run = false
    foreach (Task pingTask in pingTasks)
        await pingTask;
}

// (very) simplified explanation of changes:
// async = this method is async (and therefore awaitable)
// Task<> = This async method returns a result of type ...
// Tuple<bool, string> = A generic combination of a bool and a string
// (-)int seqnum = wasn't used so I removed it
private async Task<Tuple<bool, string>> PingStart(string ip)
{
    Ping isPing = new Ping();
    const int timeout = 2000;
    const string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    var buffer = Encoding.ASCII.GetBytes(data);
    PingOptions options = new PingOptions {DontFragment = false};

    // await SendPingAsync = Ping and wait without blocking
    PingReply reply = await isPing.SendPingAsync(ip, timeout, buffer, options);
    string rtt = reply.RoundtripTime.ToString();

    bool success = reply.Status == IPStatus.Success;
    string text;

    if (success)
    {
        text = $"{ip}" + " Success!" + $" rtt: [{rtt}]" + $"Thread: {Thread.CurrentThread.GetHashCode()} Is pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
    }
    else
    {
        text = $"{ip}" + $" Not Successful! Status: {reply.Status}" + $"Thread: {Thread.CurrentThread.GetHashCode()} Is pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
    }

    // return if the ping was successful and the status message
    return new Tuple<bool, string>(success, text);
}

这样,每个IP都会有一个循环,它会相互独立地继续,直到run设置为false

答案 1 :(得分:0)

Thread.Sleep(n)阻止当前线程n毫秒。如果我正确理解代码,它会执行 action2 ,然后暂停调用线程一秒钟。如果该线程 是主(UI)线程,则您的UI将被阻止。

也许将同时循环移动到另一个线程可以解决问题。