Derorvin
Derorvin

Reputation: 17

How can I call an async method within a sync method?

I'm trying to call an async task (SIn) within a synch method (SignIn). I need the synch method because I'm passing ref to that method. But when I'm calling the async task, the GUI is frozen. The async task is a simple login with the onedrive sdk.

I've tried to waited the task, but the GUI still frozen. I've also tried creating a new Thread, but it didn't work too. How can I call the async method?

public override bool SignIn(ref User user)
{
   try
   {
      signInEnd = false;
      signinUser = user;

      Task<bool> task = SIn();
      task.Wait();

      return task.Result;
   }
   catch(Exception e)
   {
      return false;
   }
}

public async Task<bool> SIn()
{
   var msaAuthProvider = new MsaAuthenticationProvider(
          this.oneDriveClientId,
          this.oneDriveReturnUrl,
          this.scopes,
          new CredentialVault(this.oneDriveClientId));
   await msaAuthProvider.AuthenticateUserAsync();
   driveClient = new OneDriveClient(this.oneDriveBaseUrl, msaAuthProvider);
}

Upvotes: 0

Views: 1159

Answers (2)

MikeT
MikeT

Reputation: 5500

mm8 is right that not calling async from inside a sync method is the best way to solve your issue,

remember that the public async void EventHandler() method was specifically designed for running long running tasks from a gui linked control

However it isn't always possible to rewrite an entire system to be async when only one small section needs changing

In this case you should avoid waiting for the results as this makes the async process pointless, what you can do though is break your synchronous code into 2 parts a before and after

  • the before method will prep and launch the task,
  • the after handles the results

ie

public async Task<string> GetData(int delay)
{
    await Task.Delay(delay);
    return "complete";
}

public void StartGettingData()
{
    GetData(5000).ContinueWith(t => CompleteGetData(t.Result), TaskScheduler.Current);
}

public void CompleteGetData(string message)
{
    UpdateStatus(message);
}

this method does have the added complexity of requiring you to ensure thread safety yourself, which is why the async/await functionality was introduced

Upvotes: 0

mm8
mm8

Reputation: 169390

Calling Wait() blocks the UI thread which means that the continuation of SIn(), i.e. the part that will eventually be executed once the Task returned by AuthenticateUserAsync() has completed, won't be able to execute on this thread. This results in a deadlock.

You may be able to get around this by avoiding capturing the context by calling ConfigureAwait(false) in SIn():

public async Task<bool> SIn()
{
    var msaAuthProvider = new MsaAuthenticationProvider(
           this.oneDriveClientId,
           this.oneDriveReturnUrl,
           this.scopes,
           new CredentialVault(this.oneDriveClientId));
    await msaAuthProvider.AuthenticateUserAsync().ConfigureAwait(false);
    driveClient = new OneDriveClient(this.oneDriveBaseUrl, msaAuthProvider);
}

But the "real" solution to this kind of issues is not to mix asynchronous and synchronous code, i.e. SignIn should be asynchronous and await SIn(). Don't block on asynchronous code by calling Wait() or Result:

public Task<bool> SignIn(User user)
{
    try
    {
        ...
        return await SIn();
    }
    catch (Exception e)
    {
        return false;
    }
}

Please refer to @Stephen Cleary's blog post for more information about this.

Upvotes: 1

Related Questions