Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe to run async delegate in synchronous method on UI?

Tags:

c#

async-await

If I have an application with a synchronous method, is it safe to call an async method as shown below on a UI thread or is there an issue or potential deadlock situation? I know that calling Wait will obviously cause issues, but I feel like this may work out alright.

public void MyMainMethod(){
  var getResult = Task.Run(async () => { await getResultAsync(); }).Result;
  myLabel.Text = getResult;
}

I can successfully run on a UI thread without issue, but I feel as if I may be missing something. I understand that I could use a Task and ContinueWith, but in this example, I would want to wait for the result of the async method before exiting the synchronous method.

Update / Clarification

In the example above, let's assume that the MyMainMethod is an overridden method or a property, etc. and cannot be modified to be async.

like image 662
Mike Stonis Avatar asked Jan 28 '14 01:01

Mike Stonis


People also ask

Can we use async method in synchronous?

async Main is now part of C# 7.2 and can be enabled in the projects advanced build settings. If you have a simple asynchronous method that doesn't need to synchronize back to its context, then you can use Task. WaitAndUnwrapException: var task = MyAsyncMethod();

Should I expose asynchronous wrappers for synchronous methods?

Asynchronous methods should not be exposed purely for the purpose of offloading: such benefits can easily be achieved by the consumer of synchronous methods using functionality specifically geared towards working with synchronous methods asynchronously, e.g. Task.

Does async await run synchronously?

Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.

Does async await block UI thread?

The await operator doesn't block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.


1 Answers

Let's look at your code:

public void MyMainMethod(){
  var getResult = Task.Run(async () => { await getResultAsync(); }).Result;
  myLabel.Text = getResult;
}

Regardless of what's taking place inside getResultAsync, this code is blocking the UI thread when it calls task.Result. In most cases, this is already wrong.

Further, the fact that your getResultAsync is async suggests there's already an async operation inside it. There is no reason to wrap it with Task.Run, unless you perform a mix of CPU- and IO- bound tasks inside getResultAsync. Even then, it may not be necessary (see this for more details).

You can control the await continuation context inside getResultAsync with ConfiureAwait(false), and should do so to avoid deadlocks and redundant context switches, where possible.

So, the code can be reduced to:

public void MyMainMethod(){
  var getResult = getResultAsync().Result;
  myLabel.Text = getResult;
}

As is, it still blocks the UI. To avoid blocking, you need to make it async. See Async All the Way from Best Practices in Asynchronous Programming by Stephen Cleary.

If it cannot be modified to be async (as clarified in the update to your question), then the above is the best you can get. Indeed, it still may cause a deadlock, depending on what's going on inside getResultAsync, with out without Task.Run. To avoid deadlocks, you should not attempt to access the UI thread with a synchronous call like control.Invoke inside getResultAsync, or await any tasks scheduled on the UI thread with TaskScheduler.FromCurrentSynchronizationContext.

However, usually it is possible and desirable to re-factor the code like this into an async version:

public async Task MyMainMethod(){
  var getResult = await getResultAsync();
  myLabel.Text = getResult;
}

You would be calling it from a top-level entry point of your app, like a UI event handler:

async void Button_Click(object sender, EventArg e)
{
    try
    {
        await MyMainMethod();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}
like image 143
noseratio Avatar answered Oct 15 '22 20:10

noseratio