Search code examples
c#multithreadingasync-awaitbackgroundworker

C# async/await write to textbox


Needless to say I'm new to C# and doing some basic things to learn along the way. I'm trying to understand async/await feature. I have a form with 2 textboxes and a button. When I click the button, it checks a remote computer's service status (in this case remote registry. While it checks, it writes that it is checking the registry and tells the user to wait. Then starts if the state is stopped and gets some values I look for and writes those to the second textbox.

My code works, and I'm able to get my values. But the GUI freezes during this time. I tried to implement backgroundworker, thread and Async/Wait with no success. I tried doing Task and couldn't get that to work.

All the examples shown here or on other sites simply return int while I'm trying to return a string that writes the status and the values I want to textboxes. I tried to convert it to string with no success.

Long story short, for this type of processes, what would be a better approach? Can someone show me how and comment what does what along the way just so I understand better? I want to learn how to do it but at the same time learn why and learn to write cleaner code.

Thank you everyone for taking their time in advance.

string ComputerName = "Lab01";
    public frmRegChecker()
    {
        InitializeComponent();
    }


    private void Button1_Click(object sender, EventArgs e)
    {
      Check_Status();        

    }

    private void Check_Status()
    {
        TxtBoxstatus.AppendText("Checking Remote Registry service status on computer : " + ComputerName);
        TxtBoxstatus.AppendText(Environment.NewLine);
        TxtBoxstatus.AppendText("Please wait... ");

         ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
            try
            {


                TxtBoxstatus.AppendText("The Remote Registry service status is currently set to : " + sc.Status.ToString());
                TxtBoxstatus.AppendText(Environment.NewLine);


                if (sc.Status == ServiceControllerStatus.Stopped)
                {

                    // Start the service if the current status is stopped.

                    TxtBoxstatus.AppendText("Starting Remote Registry service...");
                    TxtBoxstatus.AppendText(Environment.NewLine);
                    try
                   {
                       // Start the service, and wait until its status is "Running".
                        sc.Start();
                        sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
                        sc.Refresh();
                        sc.WaitForStatus(ServiceControllerStatus.Running);

                        // Display the current service status.
                        TxtBoxstatus.AppendText("The Remote Registry status is now set to:" + sc.Status.ToString());
                       richTextBox1.AppendText(Environment.NewLine);
                        try
                        {
                           var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
                            var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
                            string _OSVersion = (key.GetValue("CurrentVersion")).ToString();
                           richTextBox1.AppendText("OS version is : " + _OSVersion);
                            richTextBox1.AppendText(Environment.NewLine);
                       }
                        catch (InvalidOperationException)
                       {

                            richTextBox1.AppendText("Error getting registry value from" + ComputerName);
                            richTextBox1.AppendText(Environment.NewLine);
                        }
                    }
                    catch (InvalidOperationException)
                    {

                        richTextBox1.AppendText("Could not start the Remote Registry service.");
                        richTextBox1.AppendText(Environment.NewLine);
                    }
                }
                else if (sc.Status == ServiceControllerStatus.Running)
               {
                   try
                   {
                     var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
                     var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
                            string _OSVersion = (key.GetValue("CurrentVersion")).ToString();
                     richTextBox1.AppendText("OS version is : " + _OSVersion);
                     richTextBox1.AppendText(Environment.NewLine);
                    }
                    catch (InvalidOperationException)
                    {

                            richTextBox1.AppendText("Error getting registry value from" + ComputerName);
                            richTextBox1.AppendText(Environment.NewLine);
                    }

                }
           }

          }
          catch
           {                  
             richTextBox1.AppendText("Error getting registry value from " + ComputerName);
             richTextBox1.AppendText(Environment.NewLine);

           }
}

Solution

  • Unfortunately, ServiceController is a rather outdated class and doesn't natively support async/await. That would be the cleanest solution if it were possible.

    So, what you can do is use async/await with Task.Run. Task.Run executes code on a background thread, and you can use async/await from the UI to consume that background operation. This approach allows exceptions to be propagated and handled in a natural fashion, and it allows return values to also be handled naturally. Strip out the textbox updates for now for simplicity, and the first step looks like this:

    private async void Button1_Click(object sender, EventArgs e)
    {
      try
      {
        var osVersion = await Task.Run(() => CheckStatus());
        richTextBox1.AppendText("OS version is : " + osVersion);
        richTextBox1.AppendText(Environment.NewLine);
      }
      catch (InvalidOperationException ex)
      {
        richTextBox1.AppendText("Error getting registry value from" + ComputerName);
        richTextBox1.AppendText(ex.ToString());
        richTextBox1.AppendText(Environment.NewLine);
      }
    }
    
    private string CheckStatus()
    {
      ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
      if (sc.Status == ServiceControllerStatus.Stopped)
      {
        // Start the service if the current status is stopped.
        // Start the service, and wait until its status is "Running".
        sc.Start();
        sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
        sc.Refresh();
        sc.WaitForStatus(ServiceControllerStatus.Running);
      }
    
      var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
      var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
      return (key.GetValue("CurrentVersion")).ToString();
    }
    

    Next, add the progress updates. There's a built-in mechanism for that - IProgress<T>/Progress<T>, and it works like this:

    private async void Button1_Click(object sender, EventArgs e)
    {
      try
      {
        var progress = new Progress<string>(update =>
        {
          TxtBoxstatus.AppendText(update);
          TxtBoxstatus.AppendText(Environment.NewLine);
        });
        var osVersion = await Task.Run(() => CheckStatus(progress));
        richTextBox1.AppendText("OS version is : " + osVersion);
        richTextBox1.AppendText(Environment.NewLine);
      }
      catch (InvalidOperationException ex)
      {
        richTextBox1.AppendText("Error getting registry value from" + ComputerName);
        richTextBox1.AppendText(ex.ToString());
        richTextBox1.AppendText(Environment.NewLine);
      }
    }
    
    private string CheckStatus(IProgres<string> progress)
    {
      progress?.Report("Checking Remote Registry service status on computer : " + ComputerName);
      progress?.Report("Please wait... ");
    
      ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
      progress?.Report("The Remote Registry service status is currently set to : " + sc.Status.ToString());
      if (sc.Status == ServiceControllerStatus.Stopped)
      {
        // Start the service if the current status is stopped.
        progress?.Report("Starting Remote Registry service...");
        // Start the service, and wait until its status is "Running".
        sc.Start();
        sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
        sc.Refresh();
        sc.WaitForStatus(ServiceControllerStatus.Running);
        progress?.Report("The Remote Registry status is now set to:" + sc.Status.ToString());
      }
    
      var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
      var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
      return (key.GetValue("CurrentVersion")).ToString();
    }
    

    Note the separation of concerns: the only code that touches UI objects is a UI event handler. The CheckStatus method that contains the actual program logic is separated from the UI - all it knows is that it can report progress strings and return a string result.