Search code examples
c#wpf

C# use multithread to write to richtextBox


I am writing a C# script to do a ping test on a network. I have it mostly working, except the part to write to richtextbox. I need to write results as the ping test is happening. I know it needs multithreading, but I have spent the past week trying to figure it out, and cannot. Here is the code I have, minus the unnecessary parts. The part I am having trouble with is the RunPingTest function. I will also include my sample xml for the IP addresses. All the buttons and other functions otherwise work as intended.

using System.Diagnostics;          
using System.Windows;              
using System.Xml.Linq;             
using System.Collections.Generic;  
using System.Net.NetworkInformation;
using System;
using System.Threading;

namespace NetTestingTool
{
   public partial class NetTest : Window
   {
      static string dataFolder = "D:";
      static readonly string configPath = dataFolder + "\\ConfigFiles";
      
      //get HostIPAddresses.xml
      XElement GroupNames = XElement.Load(configPath + "\\HostIPAddresses.xml");
      bool testIsRunning = false;
      //Start the initial GUI

      public NetTest()
      {
         InitializeComponent();
         //populate the dropdown
         foreach (var groupElement in GroupNames.Elements("Group"))
         {
            if (groupElement.Attribute("name").Value != null)
            {
               dropDown.Items.Add(groupElement.Attribute("name").Value);
            }
         }
      }

      //********** Button Actions **********
      //run the network test
      public void BtnStart_Click(object sender, RoutedEventArgs e)
      {
         //clear richtextbox first
         resultsBox.Document.Blocks.Clear();
         //first, make sure the user has selected a group
         if (dropDown.SelectedItem != null)
         {
            string HostSelection = (string)dropDown.SelectedItem;
            //create a list of IP addresses based on group chosen
            List<string> pingList = new List<string>();
            foreach (var groupElement in GroupNames.Elements("Group"))
            {
               //resultsBox.AppendText(groupElement.Attribute("name").Value);
               if (groupElement.Attribute("name").Value == HostSelection)
               {
                  // resultsBox.AppendText(groupElement);
                  foreach (var hostSystem in groupElement.Elements("HostSystem"))
                  {
                     if (hostSystem.Attribute("IPAddress").Value != null)
                     {
                        pingList.Add(hostSystem.Attribute("IPAddress").Value);
                     }
                  }
               }
            }
            
            /*Should this be outside BtnStart_Click?
            currently cannot because it uses pingList*/
            void RunPingTest(object? obj)
            {
               //do the ping test
               resultsBox.AppendText("Doing the ping test \r"); //Just kidding, it isn't
               foreach (var pingElement in pingList)
               {
                  //this needs to be able to write to resultsBox as the ping happens (richtextbox)
                   Ping myPing = new Ping();
                   resultsBox.AppendText("Pinging " + pingElement + ". . . ");
                   PingReply reply = myPing.Send(pingElement, 2000); //ip and timeout
                   string results = reply.Status.ToString();
                   resultsBox.AppendText(results + "\r");
               }
               resultsBox.AppendText("\r\n Finished ping testing");
            }
         }
      } 
        //other buttons here
   }
}

xml for IPAddresses:

<?xml version="1.0"?>
<Systems>
   <Group name="Group1">
      <HostSystem name="System1" IPAddress="1.1.1.1"> </HostSystem> 
      <HostSystem name="System2" IPAddress="2.2.2.2"> </HostSystem>
      <HostSystem name="System3" IPAddress="3.3.3.3"> </HostSystem>  
   </Group>
   <Group name="Group2">
      <HostSystem name="System4" IPAddress="4.4.4.4"> </HostSystem>
      <HostSystem name="System5" IPAddress="5.5.5.5"> </HostSystem>
      <HostSystem name="System6" IPAddress="6.6.6.6"> </HostSystem>  
   </Group>

Solution

  • Well, in the code you posted you never call the method RunPingTest which is probably why your RichTextBox never gets updated.

    However, if you want to do this asynchronously, the Ping class contains a method called SendPingAsync with a number of overloads that returns a Task<PingReply> and allows you to just use async/await. No need to introduce any kind of Thread or BackgroundWorker or manually deal with the Dispatcher.

    private async void BtnStart_Click(object sender, RoutedEventArgs e)
    {
        var pingList = new List<string>
        {
           "1.1.1.1",
           "2.2.2.2",
           "3.3.3.3",
           "4.4.4.4"
        };
    
        resultsBox.Document.Blocks.Clear();
        resultsBox.AppendText("Doing the ping test\r");
        using (var ping = new Ping())
        {
            foreach (var ip in pingList)
            {
                resultsBox.AppendText($"Pinging {ip} . . . ");
                PingReply reply = await ping.SendPingAsync(ip, 2000);
                resultsBox.AppendText($"{reply.Status} \r");
            }
        }
    }
    

    The important thing is to make your event handler async void and to await the calls to SendPingAsync. You might also want to consider disabling 'BtnStart' until the method completes, otherwise users will be able to click on it while a ping test is still running.