0

As a result of my question about TaskCompletionSource

I tried a similar technique for getting the tokens

private t.Task<OAuthTokens> GetOAuthTokens()
    {
        var tcs = new t.TaskCompletionSource<OAuthTokens>();
        t.Task.Run(
            async () =>
            {
                var oauthService = new OAuthService(_configurationCloud);
                var code = OAuthLogin.GetAuthorizationCode(_configurationCloud);
                var response = await oauthService.GetTokensAsync(code);

                tcs.SetResult(response);
            });
        return tcs.Task;
    }

calling this using

var task1 = GetOAuthTokens();
_oAuthKeyService.OAuthResponse = task1.Result;

However the program locks up when I run it.

The following works OK

        var oauthService = new OAuthService(_configurationCloud);
        var code = OAuthLogin.GetAuthorizationCode(_configurationCloud);  // causes a login dialog
        var tokens = oauthService.GetTokens(code);
        _oAuthKeyService.OAuthResponse = tokens;

and brings up the authorisation dialog box.

Community
  • 1
  • 1
Kirsten Greed
  • 11,170
  • 26
  • 117
  • 234

1 Answers1

4

When I answered your previous question I assumed you had a requirement to use a TaskCompletionSource object so I am sorry if this has sent you in the wrong direction. As Paulo has said you don't normally need to use TaskCompletionSource with async/await code but you do need to understand a bit more how to use it.

Calling Result on a Task will cause that thread to block, now in a non-UI thread this isn't such an issue (just not ideal) but in a UI thread this will effectively stop your UI from responding until the task has completed, assuming it just doesn't stop completely due to a deadlock.

The thing is to learn how to use async/await in a UI environment because to get it to work you have too use async/await everywhere otherwise you will end up trying to use Task.Result to access your data and get a blocked UI thread for your troubles.

This is a good starter guide - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Now I assume you are pulling the code from the page like this (cobbled from code samples on GitHub) and then fetching the tokens.

private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    var content = webBrowser1.DocumentText;
    var regex = new Regex(@"\<title\>(.+?)=(.+?)\</title\>");
    var match = regex.Match(content);
    if (!match.Success || match.Groups.Count != 3)
        return;

    switch (match.Groups[1].Value.ToLowerInvariant())
    {
        case "code": // we have a code
            var code = match.Groups[2].Value;
            var config = new ApiConfiguration(Configuration.ClientId, Configuration.ClientSecret, Configuration.RedirectUrl);
            var service = new OAuthService(config, new WebRequestFactory(config));
            var tokens = service.GetTokensAsync(code).Result; // <= blocking
            _keyService.OAuthResponse = tokens;
            break;

        case "error": // user probably said "no thanks"
            webBrowser1.Navigate(_logoffUri);
            break;
    }
} 

however your code is blocking on the .Result

What you need to do is use async/await all the way up however when you use await it complains about the lack of async on the method, so, just add it; yes this is allowed and there are many articles and blog posts about this that catch people new to async/await in winforms/wpf UI.

e.g.

// we add async to the callback - yup it's allowed 
private async void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    var content = webBrowser1.DocumentText;
    var regex = new Regex(@"\<title\>(.+?)=(.+?)\</title\>");
    var match = regex.Match(content);
    if (!match.Success || match.Groups.Count != 3)
        return;

    switch (match.Groups[1].Value.ToLowerInvariant())
    {
        case "code": // we have a code
            var code = match.Groups[2].Value;
            var config = new ApiConfiguration(Configuration.ClientId, Configuration.ClientSecret, Configuration.RedirectUrl);
            var service = new OAuthService(config, new WebRequestFactory(config));
            var tokens = await service.GetTokensAsync(code); // <= now we can use await here => non-blocking
            _keyService.OAuthResponse = tokens;
            break;

        case "error": // user probably said "no thanks"
            webBrowser1.Navigate(_logoffUri);
            break;
    }
} 

I've uploaded the code as gist I hope it helps

Community
  • 1
  • 1
Shaun Wilde
  • 7,860
  • 4
  • 32
  • 52
  • Thanks Shaun. I get that the following blocks: var tokens = service.GetTokensAsync(code).Result; I get that I can use service.GetTokens() instead, which will also block. What I dont understand is why the login dialog only appears when I use service.GetTokens(). When I use the Async option the program locks up and never unlocks. It is as though the login dialog is invisible. – Kirsten Greed Sep 05 '15 at 04:16
  • 2
    @kirsteng - with the small snippets supplied it is hard to tell - GetTokens implementation is [available](https://github.com/MYOB-Technology/AccountRight_Live_API_.Net_SDK/blob/master/MYOB.API.SDK/SDK/Services/OAuthService.cs#L64) on github actually uses an alternative asynchronous approach that does not rely on the async/await pattern - so it can be used with versions of the framework that do not have async/await e.g. .NET2. – Shaun Wilde Sep 05 '15 at 05:10
  • Thanks Shaun, I am just going to use the non asynchronous version of GetTokens() for the moment. – Kirsten Greed Sep 06 '15 at 02:55