0

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.

Zigzagoon
  • 787
  • 6
  • 16

5 Answers5

2

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.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
1

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).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
-1

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

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • This solution seems like it would require refactoring of the upper call stack, which is far beyond the scope in my scenario – Zigzagoon Feb 22 '21 at 20:24
  • Oh I see. So you want to be able to call and await an async method from inside a method that is not async? – John Wu Feb 22 '21 at 20:31
  • @JohnWu Yes, I wish to have solution that can call async tasks within the IAsyncResult method without having to refactor the IAsyncResult method signature. – Zigzagoon Feb 22 '21 at 20:49
-1

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

Rikki
  • 3,338
  • 1
  • 22
  • 34
  • Their requirements aren't to make the method asynchronous, it's just to change the API of how it reports its *asynchronous* result to the caller. – Servy Feb 22 '21 at 20:32
-3

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
}
kristofke
  • 285
  • 2
  • 3