为什么在使用 ContinueWith 执行任务时 Windows 窗体 UI 被阻止? [英] Why is the Windows Forms UI blocked when executing Task with ContinueWith?

查看:23
本文介绍了为什么在使用 ContinueWith 执行任务时 Windows 窗体 UI 被阻止?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我花了几天时间在 Google 中搜索并试图了解为什么在我的情况下,在 任务 中执行 ping 时,Windows 窗体 UI 被阻止.我看到了很多类似的案例,但没有一个能解释我的具体案例.

I spent a couple of days searching in Google and trying to understand why in my case Windows Forms UI is blocked when executing pings in Tasks. I saw a lot of similar cases, but none of them explains my specific case.

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

I have an application which sends pings asynchronously. Each ping is send inside of a Task. I use .ContinueWith to receive result of a ping and print it to textbox without blocking UI thread. It works OK if I launch all pings once. If I add a while {run} loop to make them run forever my UI becomes unresponsive and blocked, and none of the results are printed to the textbox.

有问题的代码:

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 的情况下仍然能够使用 Tasks 进行 ping 操作?
  3. 有没有更好的方法可以同时向多个 IP 地址发起无休止的 ping 操作?
  1. Why is the UI blocked with a while loop and why is it not blocked without it?
  2. How can I modify my code to be still able to use Tasks for pings without blocking the UI?
  3. Is there a better way to launch endless pings to several IP addresses simultaneously?

完整代码:

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

推荐答案

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

Since you already create (and save) your tasks, the easiest fix would be to await them for each iteration of your while loop:

while (run)
{
    action2();

    foreach (Task t in continutask)
        await t;
}

这样,当所有 ping 完成(成功与否)后,您将再次开始整个过程​​ - 毫不拖延.

That way, when all pings completed (successful or not) you start the entire process again - without delay.

还有一件事:你可以添加一个 textBox1.ScrollToEnd();PrintResult

One more thing: You could add a textBox1.ScrollToEnd(); to PrintResult

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

Since there is a lot of room for improvement, below is a rewritten and simplified example. I've removed a lot of unused variables (e.g. seqnum) and made the PingStart method completely asynchronous. I also replaced your ListBox with a TextBox for easier testing, so you might want to revert that in your code.

这仍然不是所有可能实现中最干净的(主要是因为全局run),但它应该向您展示如何做more异步" :)

This still isn't the cleanest of all possible implementations (mainly because of the global run) but it should show you how to do things "more async" :)

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.

This way you will have a loop for each IP that will continue independently of each other until run is set to false.

这篇关于为什么在使用 ContinueWith 执行任务时 Windows 窗体 UI 被阻止?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆