代码之家  ›  专栏  ›  技术社区  ›  Dementy Yakovliev

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

  •  -3
  • Dementy Yakovliev  · 技术社区  · 7 年前

    我花了几天时间在谷歌上搜索,试图弄明白为什么我使用的是Windows窗体 UI被阻止 在中执行ping时 任务 . 我看到了很多类似的案例,但没有一个能解释我的具体案例。

    问题描述:

    我有一个异步发送ping的应用程序。每个ping都在任务内部发送。我使用 .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. 如何修改代码,使其仍然能够在不阻塞用户界面的情况下使用ping任务?
    3. 有没有更好的方法可以同时对多个IP地址进行无休止的ping?

    完整代码:

    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 回复  |  直到 7 年前
        1
  •  1
  •   Manfred Radlwimmer Michael Canan    7 年前

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

    while (run)
    {
        action2();
    
        foreach (Task t in continutask)
            await t;
    }
    

    这样,当所有ping完成后(无论成功与否),您就可以立即重新启动整个过程。

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


    由于还有很大的改进空间,下面是一个重写和简化的示例。我删除了很多未使用的变量(例如。 seqnum )并使 PingStart 方法完全异步。为了便于测试,我还用文本框替换了您的列表框,所以您可能希望在代码中还原它。

    这还不是 最干净的 所有可能的实现(主要是因为 run )但它应该告诉你怎么做 “更异步” :)

    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都将有一个循环,该循环将彼此独立地继续,直到 设置为 false .

        2
  •  0
  •   Gábor    7 年前

    线睡眠(n) 阻止当前线程n毫秒。如果我正确理解代码,它就会执行 行动2 然后暂停 使命感 线程一秒钟。如果那根线 主(UI)线程,您的UI将被阻止。

    可能会移动 虽然 循环到另一个线程将解决此问题。

    推荐文章