Home > Back-end >  How to properly use await and async in C#?
How to properly use await and async in C#?

Time:04-15

I am creating a game on Unity which will run a python script in the backend using C#, the script consists of an algorithm which takes time to load and give its output, hence I decided to use asynchronous programming. Here is my code in C#:

   public static BERT instance;
   public string valueSaved;
   public void exec()
   {
       p.StartInfo = new ProcessStartInfo(pythonFile, fileName)
       {
           RedirectStandardOutput = true,
           UseShellExecute = false,
           CreateNoWindow = true
       };
       p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
       p.Start();
       string output = p.StandardOutput.ReadToEnd();
       p.WaitForExit();
       valueSaved = output;
   }
   public async void Activate()
   {
      await Task.Run(() => exec());
   }

This Activate() method is called in another class method:

private void runBERT()
    {
       Debug.Log("Executing BERT");
       BERT.instance.Activate();
    }

The code seems to be working fine, as when the runBERT() method is called, the valueSaved stores the output from the script after its execution, and the game stays fully resumed during this period, otherwise if I directly execute exec() method, the game freezes until the script has fully run. However, this is the first time I have applied an async method and all I know is that the await keyword allows the method to leave the current thread and run in the background, but I will be implementing more of these methods, so I am not sure what I am implementing is fine for the long run, do I need to do anything else? I am just asking this to be on the safe side, so I don't run into errors or exceptions later on. The main thing I want is to store the value returned by the script in the background, which will be called later on. Your help is much appreciated.

CodePudding user response:

the await keyword allows the method to leave the current thread and run in the background

Not really. It's actually the Task.Run that makes things run on a thread pool thread.

do I need to do anything else?

As a general rule, you should avoid async void. Instead of having Activate be an async void method, it should be an async Task method. That way you know when the external process completes (when the task completes).

You can also use Task<T> to include a return type by returning output instead of setting it to a member variable. And you can treat Task<T> as member variables too, i.e., changing the type of valueSaved from string to Task<string>. That would allow all other code to do await valueSaved; to asynchronously wait for the process to complete and get its output.

CodePudding user response:

You could make the Activate method properly asynchronous, without blocking a thread with Task.Run, by using the Process.WaitForExitAsync method (available from .NET 5 onwards).

public async Task<string> ActivateAsync()
{
    Process p = new Process();
    p.StartInfo.FileName = pythonFile;
    p.StartInfo.Arguments = fileName;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.CreateNoWindow = true;
    p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    p.Start();
    await p.WaitForExitAsync().ConfigureAwait(false);
    string output = p.StandardOutput.ReadToEnd();
    return output;
}

Usage:

string output = await BERT.instance.ActivateAsync();

No need for storing the output in a string field, as a side-effect, and risking race conditions. The Result is stored inside the asynchronous Task<string>, and it's propagated simply by awaiting the task.

  • Related