How to call async Task within IAsyncResult method

1.4k Views Asked by At

I have an existing method with return type IAsyncResult from older code, how can I call an async Task from within this method? For example:

public IAsyncResult BeginXXX()
{
    // I want to call my async Task here, but it cannot compile like this:
    var value = await DoMyTaskAsync();
    DoSomethingWithValue(value);

    // existing code
    return new SomeOtherAsyncResult();
}

Edit: The solution would preferably not have to refactor the method signature, that would involve work beyond the scope.

5

There are 5 best solutions below

3
John Wu On

This is a common problem. You solve this using TaskCompletionSource.

Let's say you have a legacy class and it has the old-style async methods BeginBar and EndBar. The BeginBar accepts an AsyncCallback delegate.

public Task<int> Foo()
{
    var source = new TaskCompletionSource<int>();
    someLegacyClass.BeginBar( x => source.TrySetResult(someLegacyClass.EndBar(x)); }
    return source.Task;
}

See interop with other asynchronous patterns

0
kristofke On

You're missing an async keyword in your function definition.
You'll also need to return a Task<T>.

Try something like this:

public async Task<IAsyncResult> BeginXXX()
{
    await DoMyTaskAsync();

    // the rest of your code
}
1
Rikki On

You "could" ignore every good in Asynchronous Programming when calling DoMyTaskAsync function and do this:

public IAsyncResult BeginXXX()
{
    // I want to call my async Task here, but it cannot compile like this:
    var value = DoMyTaskAsync().Result;
    DoSomethingWithValue(value);

    // existing code
    return new SomeOtherAsyncResult();
}

Or, you could read about what async does to a function. All DoMyTaskAsync does really, if you don't use await when calling it, is that it's returning a Task<T> instance.

You can operate a Task instance normally without relying on await. Have a read on Task-based Asynchronous Programming

0
Nkosi On

Since Task is derived from IAsyncResult,

public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable ...

Wrap the refactored code in a Task.Run In order to maintain the current API and still be able to use async-await

public IAsyncResult BeginXXX() {        
    return Task.Run(async () => {
        var value = await DoMyTaskAsync(); // I want to call my async Task here
        DoSomethingWithValue(value);

        // existing code
        return new SomeOtherAsyncResult();
    });
}

this should work for the scope of shown example.

I highly suggest this only be a temporary fix as this could potentially cause deadlocks up the stack with the mixing of async and blocking calls.

This code and the code that depends on it should eventually be refactored to follow the recommendations of the common documentation provided for asynchronous code.

0
Stephen Cleary On

I have an existing method with return type IAsyncResult from older code, how can I call an async Task from within this method?

The answer is in the TAP interop documentation. Essentially you return a TaskCompletionSource<T>.Task.

Here's some helpers from my AsyncEx library that make this much easier:

public static IAsyncResult ToBegin<TResult>(Task<TResult> task, AsyncCallback callback, object state)
{
  var tcs = new TaskCompletionSource<TResult>(state, TaskCreationOptions.RunContinuationsAsynchronously);
  var oldContext = SynchronizationContext.Current;
  SynchronizationContext.Current = null;
  try
  {
    CompleteAsync(task, callback, tcs);
  }
  finally
  {
    SynchronizationContext.Current = oldContext;
  }
  return tcs.Task;
}

// `async void` is on purpose, to raise `callback` exceptions directly on the thread pool.
private static async void 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
  {
    callback?.Invoke(tcs.Task);
  }
}

public static TResult ToEnd<TResult>(IAsyncResult asyncResult) =>
    ((Task<TResult>)asyncResult).GetAwaiter().GetResult();

Note that the TaskCompletionSource<T> is necessary to have the proper semantics for your state argument.

With these, your implementation can look like:

public IAsyncResult BeginXXX(...rest, AsyncCallback callback, object state) =>
    XXXAsync(...rest).ToBegin(callback, state);
public EndXXX(IAsyncResult asyncResult) => TaskExtensions.ToEnd<MyResult>(asyncResult);

public async Task<MyResult> XXXAsync(...rest)
{
  var value = await DoMyTaskAsync();
  DoSomethingWithValue(value);
  return myResult;
}

Note that this assumes you're going to translate everything at this level into async/await and get rid of the new SomeOtherAsyncResult. However, if you have a lower-level IAsyncResult-based API that you are not ready to convert to async/await and that you still need to call, then your XXXAsync method can use TAP-over-APM:

public async Task<MyResult> XXXAsync(...rest)
{
  var value = await DoMyTaskAsync();
  DoSomethingWithValue(value);
  return await TaskFactory.FromAsync(BeginOtherAsyncResult, EndOtherAsyncResult,
      ...restOtherAsyncResultArgs, null);
}

In this case you end up with APM-over-TAP (to keep your BeginXXX interface) and TAP-over-APM (to continue using the OtherAsyncResult interface), with your TAP method sandwiched between (XXXAsync).