I want to convert asynchronous action delegates to asynchronous function delegates that return a specified value. I have come up with an extension method for this:
public static Func<Task<TResult>> Return<TResult>(this Func<Task> asyncAction, TResult result)
{
ArgumentValidate.NotNull(asyncAction, nameof(asyncAction));
return async () =>
{
await asyncAction();
return result;
};
}
However, my extension method is buggy in that exceptions that would have been delivered synchronously from the action delegate now get delivered asynchronously from the function delegate. Concretely:
Func<Task> asyncAction = () => { throw new InvalidOperationException(); };
var asyncFunc = asyncAction.Return(42);
var task = asyncFunc(); // exception should be thrown here
await task; // but instead gets thrown here
Is there a way of creating this wrapper in such a way that synchronous exceptions continue to delivered synchronously? Is ContinueWith
the way to go?
Update: A concrete example of an asynchronous operation that throws exceptions synchronously:
public static Task WriteAllBytesAsync(string filePath, byte[] bytes)
{
if (filePath == null)
throw new ArgumentNullException(filePath, nameof(filePath));
if (bytes == null)
throw new ArgumentNullException(filePath, nameof(bytes));
return WriteAllBytesAsyncInner(filePath, bytes);
}
private static async Task WriteAllBytesAsyncInner(string filePath, byte[] bytes)
{
using (var fileStream = File.OpenWrite(filePath))
await fileStream.WriteAsync(bytes, 0, bytes.Length);
}
Test:
Func<Task> asyncAction = () => WriteAllBytesAsync(null, null);
var asyncFunc = asyncAction.Return(42);
var task = asyncFunc(); // ArgumentNullException should be thrown here
await task; // but instead gets thrown here
Well, you won't be able to use async
in the initial call. That much is clear. But you can use a synchronous delegate which calls the function, and then captures the returned task to await it inside an async delegate:
public static Func<Task<TResult>> Return<TResult>(this Func<Task> asyncAction, TResult result)
{
ArgumentValidate.NotNull(asyncAction, nameof(asyncAction));
return () =>
{
// Call this synchronously
var task = asyncAction();
// Now create an async delegate for the rest
Func<Task<TResult>> intermediate = async () =>
{
await task;
return result;
};
return intermediate();
};
}
Alternatively, refactor it into two methods, basically extracting the asynchronous lambda expression into an async method:
public static Func<Task<TResult>> Return<TResult>(
this Func<Task> asyncAction, TResult result)
{
ArgumentValidate.NotNull(asyncAction, nameof(asyncAction));
return () =>
{
var task = asyncAction();
return AwaitAndReturn(task, result);
};
}
public static async Func<Task<TResult>> AwaitAndReturn<TResult>(
this Task asyncAction, TResult result)
{
await task;
return result;
}
See more on this question at Stackoverflow