1

I am implementing TokenProvider class from Microsoft.ServiceBus namespace. I am overriding the following method

protected override IAsyncResult OnBeginGetToken(string appliesTo, string action, TimeSpan timeout, AsyncCallback callback,
            object state)
{
    var token = GetCustomTokenAsync(appliesTo); //How to await?
     return new CompletedAsyncResult<SharedAccessSignatureToken>(token, callback, state);
}

But as mentioned in the comment above. GetCustomTokenAsync is an async method and how do I await without disturbing the signature of TokenProvider class? I wanted to make use of async nature of GetCustomTokenAsync and hence I am reluctant to use .Result. Is there any better way of solving this?

Rockstart
  • 2,337
  • 5
  • 30
  • 59

2 Answers2

2

You need to create APM wrappers around a TAP method.

This is not entirely straightforward, especially because the End* methods of TokenProvider do not follow the APM pattern.

Requirements and recommendations:

  • Task implements IAsyncResult, so you can return a task.
  • The state member has to be returned from the IAsyncResult.AsyncState property on that task, so the task you return cannot be one generated by async - you have to create it yourself via TaskCompletionSource<T>.
  • The callback needs to be invoked when the operation completes.
  • If you override Begin*, you must also override the matching End*.

The resulting code ends up being a bit complex. You can avoid the boilerplate by using the ApmAsyncFactory.ToBegin in my AsyncEx.Tasks library (currently, ApmAsyncFactory is only in the prerelease build):

protected override IAsyncResult OnBeginGetToken(string appliesTo, string action,
    TimeSpan timeout, AsyncCallback callback, object state)
{
  return ApmAsyncFactory.ToBegin(GetCustomTokenAsync(appliesTo), callback, state);
}

protected override SecurityToken OnEndGetToken(IAsyncResult result,
    out DateTime cacheUntil)
{
  var ret = ApmAsyncFactory.ToEnd<SharedAccessSignatureToken>(result);
  cacheUntil = ...;
  return ret;
}

Alternatively, if you wanted to see how the sausage is made and do it all by hand, one option would be (keeping in mind where all the exceptions can go):

protected override IAsyncResult OnBeginGetToken(string appliesTo, string action,
    TimeSpan timeout, AsyncCallback callback, object state)
{
  var tcs = new TaskCompletionSource<SharedAccessSignatureToken>(state,
      TaskCreationOptions.RunContinuationsAsynchronously);
  var _ = CompleteAsync(GetCustomTokenAsync(appliesTo), callback, tcs);
  // _ is ignored; it can never fault.
  return tcs.Task;
}

private static async Task CompleteAsync<TResult>(Task<TResult> task,
    AsyncCallback callback, TaskCompletionSource<TResult> tcs)
{
  try
  {
    tcs.TrySetResult(await task.ConfigureAwait(false));
  }
  catch (OperationCanceledException ex)
  {
    tcs.TrySetCanceled(ex.CancellationToken);
  }
  catch (Exception ex)
  {
    tcs.TrySetException(ex);
  }
  finally
  {
    // Invoke callback unsafely on the thread pool, so exceptions are global
    if (callback != null)
      ThreadPool.QueueUserWorkItem(state => callback((IAsyncResult)state), tcs.Task);
  }
}

protected override SecurityToken OnEndGetToken(IAsyncResult result,
    out DateTime cacheUntil)
{
  var task = (Task<SharedAccessSignatureToken>)result;
  var ret = task.GetAwaiter().GetResult();
  cacheUntil = ...;
  return ret;
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Doesn't Task implement `IAsyncResult`? Why not use a `ContinueWith` to map to `CompletedAsyncResult` and return that task? – Panagiotis Kanavos Oct 03 '16 at 12:13
  • The task returned from `ContinueWith` will not have the correct `IAsyncResult.AsyncState`. Also, using `CompletedAsyncResult` at all makes the implementation synchronous rather than asynchronous. – Stephen Cleary Oct 03 '16 at 14:03
-3

I wouldn't use IAsyncResult at all, instead you should return a Task<T> and await that. Same thing goes for your GetCustomTokenAsync method.

ThePerplexedOne
  • 2,920
  • 15
  • 30
  • 1
    That does not help with implementing a class that inherits from `TokenProvider`, which is the goal here. – svick Oct 01 '16 at 14:26