ConfusedGuy
ConfusedGuy

Reputation: 121

Using async instead of Task.Run()

I have the following piece of code:

private void btnAction_Click(object sender, RoutedEventArgs e)
{

    /** Clear the results field */
    txtResult.Text = "";

    /** Disable the button and show waiting status */
    btnAction.IsEnabled = false;
    lblStatus.Text = "Wait...";

    /** Get input from the query field */
    string input = query.Text;

    /** Run a new task */
    Task.Run(() => {

        // calling a method that takes a long time (>3s) to finish and return
        var attempt = someLibrary.doSomethingWith(input);

        // return the result to the GUI thred
        this.Dispatcher.Invoke(() =>
        {

            if (attempt.ContainsKey("success"))
            {
                if (attempt["success"] == true)
                {
                    txtResult.Text = "Success! The door is: " + (attempt["is_open"] ? "open" : "closed");
                    lblStatus.Text = "";
                }
                else
                {
                    lblStatus.Text = "Error! The service says: " + attempt["errorMessage"];
                }
            }

            else
            {
                MessageBox.Show("There was a problem getting results from web service.");
                lblStatus.Text = "";
            }

            /** Re-enable the button */
            btnAction.IsEnabled = true;

        });
    });

}

Now, I would like to:

Hence why I want to write this using the async model.

What do I do?

Upvotes: 3

Views: 352

Answers (2)

TheGeneral
TheGeneral

Reputation: 81473

You would mark your method as async, await the Task.Run so the continuations run on the UI, also leaving only the long running (seemingly CPU bound) job within it

private async void btnAction_Click(object sender, RoutedEventArgs e)
{
   btnAction.IsEnabled = false;
   txtResult.Text = "";       
   lblStatus.Text = "Wait...";

   string input = query.Text;

   // calling a method that takes a long time (>3s) to finish and return
   var attempt =  await Task.Run(() => someLibrary.doSomethingWith(input));

   if (attempt.ContainsKey("success"))
   {
      if (attempt["success"] == true)
      {
         txtResult.Text = "Success! The door is: " + (attempt["is_open"] ? "open" : "closed");
         lblStatus.Text = "";
      }
      else
      {
         lblStatus.Text = "Error! The service says: " + attempt["errorMessage"];
      }
   }  
   else
   {
      MessageBox.Show("There was a problem getting results from web service.");
      lblStatus.Text = "";
   }

   btnAction.IsEnabled = true;

}

Update

To cancel the task, you would use a CancellationToken from am instance of CancellationTokenSource and pass that into Task.Run and also your long running method to check for IsCancellationRequested (if you can). You cancel by calling CancellationTokenSource.Cancel

Note you would likely want to wrap this in a try catch finally and catch on OperationCanceledException and place your button enabling code in the finally

Upvotes: 5

Ben
Ben

Reputation: 2209

The async modifier requires that the function return Task<T> (or void, in which case any await statements will be ignored). This means that using async and using Task.Run() are one and the same, the premise of your question doesn't make sense.

However, what I think you want to do is just use the async await syntax to avoid an explicit call to Task.Run().

Callbacks

Just create a function which returns a Task

async Task<T> Foo()

And then assign a variable var bar=await Foo();

Cancel a running task

Just use CancellationToken

CancellationTokenSource tokenSource = new CancellationTokenSource();

If you construct a task with two arguments, the second argument is a cancellation token:

var bar= new Task(action, tokenSource.Token)

This lets you use tokenSource.Cancel();

Relevant link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8

Chaining Calls

If you don't require a defined order of execution, you can use Task.WhenAll(), otherwise you can either execute the next task inside the previous one or in the awaiting code.

Task.WhenAll(): https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=netframework-4.8#System_Threading_Tasks_Task_WhenAll_System_Collections_Generic_IEnumerable_System_Threading_Tasks_Task__

Upvotes: 2

Related Questions