1

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

           }
}
Besiktas
  • 331
  • 1
  • 6
  • 23
  • A Background worker should work for this, and this is the sort of job those are designed for. What problems did you have with a Background worker? – Erik Funkenbusch Aug 10 '17 at 15:59
  • Hi Erik, Thanks for the quick reply. The examples I've seen with backgroundworker were all with progressbar which was reporting an integer to the label result. I just couldn't implement how it returns my status report in string to the textbox. I get errors like you cannot access the textbox from another control type of stuff. – Besiktas Aug 10 '17 at 16:03
  • 1
    You *say* that you can't get an `async` solution working, yet you've shown no async code at all. – Servy Aug 10 '17 at 17:00
  • Hi Servy, I'm not sure which solution to go with and why. That's why I asked that first. There are too many ways to do what I'm trying to do and I'm trying to learn the cleanest yet the best approach. – Besiktas Aug 10 '17 at 17:05
  • @Besiktas You said that you wrote an asynchronous solution, but it didn't work, yet you haven't shown an asynchronous solution. Show your asynchronous solution, and explain why it doesn't work. – Servy Aug 10 '17 at 17:18
  • Please read my previous comment – Besiktas Aug 10 '17 at 17:41
  • @Besiktas Please read my previous comment. – Servy Aug 10 '17 at 18:32

3 Answers3

3

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you so much Stephen. so on the line `var osVersion = await Task.Run(() => CheckStatus()); ` what argument do I give? Is there an "updated" version for servicecontroller that is faster or better? Also does the "progress?" means "if progress" Thanks again. This really explains a lot. Scares me how much I don't know tho. – Besiktas Aug 11 '17 at 00:14
  • You pass progress into it. The `progress?.Report(..);` is shorthand for `if (progress != null) progress.Report(..);` – Stephen Cleary Aug 11 '17 at 00:50
  • Thanks again. Yes I added that and it worked. :) Thank you so much. – Besiktas Aug 11 '17 at 00:52
1

This can easily be accomplished using async/await. An Example:

A simple WPF form:

<Window x:Class="WpfApp1.MainWindow"
    ...>
  <Grid>
    <Button Name="button" Content="Start" Click="Button_Click"/>
    <TextBox Name="textButton" />
  </Grid>
</Window>

and the associated code-behind:

public partial class MainWindow:Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private async void Button_Click(object sender, RoutedEventArgs e)
    {                        
           textButton.Text = "Running...";
           string result = await DoIt();
           textButton.Text = result;
    }

    private async Task<string> DoIt()
    {
        await Task.Delay(3000);
        return "Finished...";
    }
}

When clicking the button, the long-running 'calculation' in 'DoIt' is started'asynchrounously. While it is running, the UI remains responsive. When DoIt returns returns its result (in this example after a dummy delay of 3 seconds) the 'click' eventhandler continues...

Remarks:

  • this example uses code behind for simplicity. The technique works as well using the MVVM-pattern (where the async operation is started from a command).

  • 'async void' is an anti pattern, but is used to comply withe the auto generated eventhandler in code behind.

  • in a real world application the async 'DoIt' method would propably be situated in a backend 'model class'.

  • the example assumes you only want to update the UI once the long-running operation has finished. If you want intermediate updates, there are several options (unfortunately a bit more complex).

Johan Donne
  • 3,104
  • 14
  • 21
0

I think you can still use the backgroundworker to make your textboxes display messages without freezing the GUI. Take reference from this question Here on how to return an object from backgroundworker.

  • A BGW is for long running CPU bound work, not for IO bound work. Additionally, there's no real reason to use that over a TPL based solution, which is what the OP said they're looking for. – Servy Aug 10 '17 at 17:02