2
votes

I'm doing some OAuth work, where I get my refresh token through an provided async API method (GetRefreshTokenAsync):

public async Task<Tokens> RenewAuthentication()
{
    AppTokenResult token = await OAuth.GetRefreshTokenAsync("clientid",
      "clientsecret", 
    @"myRefreshToken");

    string accessToken = token.AccessToken;
    string refreshToken = token.RefreshToken;

    return new Tokens(accessToken, refreshToken);

}

If I call this like from a non-async method as such:

public void noAsync()
{
    var r = RenewAuthentication();
    var x = r.Result;
}

It deadlocks the app :(. If I remove the 2nd line (r.Result), then it works, but that's crap, because I can't get the result. I tried reading

http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

But after trying his method 1 by adding .ConfigureAwait(false) to the GetRefreshTokenAsync() method it didn't make any difference.

Any advice?

4
So the UI should work while the result from is not received? - Thulur
What platform is this running on? Console, WPF, iOS, ASP.NET...? And why are you calling an async method from a sync one? Recommendation is to use async from beginning to end. - Krumelur
The UI can do work or not work while the result is received. While I would prefer the UI to be blocked, that's not the real issue, right? The issue is that the whole thing is deadlocking. Running on WinForms. I'm calling the async from the form load. When the form first loads I want to and let the user OAuth so I can store their tokens. - Prof
@Yaschur, works if make my calling method async then use an await to extract the task result. However, how would I extract my Task result from var r = RenewAuthentication().ConfigureAwait(false) if I call from a sync method? - Prof
That's simple - store the task from RenewAuthentication, and then await renewTask.ConfigureAwait(false). That said, I don't think you need the task anyway - just keep the awaitable around, and use renewAwaitable.GetResult(). If you're not hooking continuations, there's no need to keep the task. The awaitable has a better way to synchronously get the result :) - Luaan

4 Answers

5
votes

It deadlocks the app :(

That's what blocking on an async method in an environment that has a custom synchronization will do.

If you call this on "FormLoad", you don't need to block. Simply mark you event handler as async so you can await on the operation:

public Form()
{
    this.Load += OnLoadHandler;
}

public async void OnLoadHandler(object sender, EventArgs e)
{
    var result = await RenewAuthenticationAsync();
    // Store the result here
}
1
votes

If you access the Result property of a Task, it will act as a Future and it will block the current thread until the Task returns with the result.

I don't think you actually got a deadlock, but the Task you're waiting for never finishes.

Make sure your call to OAuth.GetRefreshTokenAsync returns.

1
votes

You need to make sure the result of RenewAuthentication isn't marshalled back to the UI thread. ConfigureAwait(false) is pretty much exactly for a purpose like this:

var renewAwaitable = RenewAuthentication().ConfigureAwait(false);
var result = renewAwaitable.GetAwaiter().GetResult();

However, I'd suggest with going all-out with async instead. There isn't really a reason to block anywhere in a UI application - just disable whatever controls must not be used while waiting for the token to come back, and enable them when it does.

-1
votes

async code is an all the way construct. You cannot do half async and should not try to force it. noAsync appears to do nothing useful here.

In his wisdom, Mr Cleary makes it clear that each async method has its own context in which it may run continuation on the UI.

Can you be sure that OAuth.GetRefreshTokenAsync is using ConfigureAwait(false) internally? If it doesn't, you will deadlock.