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;
}