2
votes

I am using the DropNet API for connecting to DropBox. I am struggling around async/await concepts though.

I have a method is calling the api GetTokenAsync. The return type is void, and there is a success and failure action to callback.

public async Task<GetTokenResult> GetAuthorizationUrl()
{
    var result = new GetTokenResult();

    _dropNetClient.GetTokenAsync(
        login =>
            {
                result.Url = _dropNetClient.BuildAuthorizeUrl(_authorizationCallback.ToString());
                result.Success = true;
            },
        exception =>
            {
                result.Error = exception.ToDiagnosticString();
                result.Success = false;
            }
        );

    return result;
}

The problem? I am thinking that changing the return type to just GetTokenResult may return faster than the actions will, therefore my results will never get set. I cannot await the async method as it returns void.

This is the one concept around async/await that I cannot wrap my head around.

1

1 Answers

6
votes

You might want to consider using a TaskCompletionSource:

public Task<GetTokenResult> GetAuthorizationUrl()
{
    var tcs = new TaskCompletionSource<GetTokenResult>();   
    _dropNetClient.GetTokenAsync(
        login => tcs.SetResult(new GetTokenResult { 
            Url = _dropNetClient.BuildAuthorizeUrl(
                      _authorizationCallback.ToString()),
            Success = true
        },
        exception => tcs.SetResult(new GetTokenResult { 
            Error = exception.ToDiagnosticString(),
            Success = true
        });
    return tcs.Task;
}

The returned task will only complete when the GetTokenAsync operation completes (via one of the callbacks), and you can await it from an async method.

I would personally use SetException instead of SetResult on failure though - so that if you await the returned task, it will throw the appropriate failure on an exception, rather than just setting the value differently. It's more idiomatically .NET-like.

EDIT: You could then change the code to return Task<string> instead:

public Task<string> GetAuthorizationUrl()
{
    var tcs = new TaskCompletionSource<string>();   
    _dropNetClient.GetTokenAsync(
        login => tcs.SetResult(_dropNetClient.BuildAuthorizeUrl
                                    _authorizationCallback.ToString()),
        exception => tcs.SetException(exception));
    return tcs.Task;
}

Now the exception would be propagated within the task itself - if you await a task which faults, the exception is thrown at that point. No need for extra properties, etc - it's much closer to how you'd write the corresponding synchronous code.