I got an async method working like an enhanced Task.WhenAll. It takes a bunch of tasks and returns when all are completed.
public async Task MyWhenAll(Task[] tasks) {
...
await Something();
...
// all tasks are completed
if (someTasksFailed)
throw ??
}
My question is how do I get the method to return a Task looking like the one returned from Task.WhenAll when one or more tasks has failed?
If I collect the exceptions and throw an AggregateException it will be wrapped in another AggregateException.
Edit: Full Example
async Task Main() {
try {
Task.WhenAll(Throw(1), Throw(2)).Wait();
}
catch (Exception ex) {
ex.Dump();
}
try {
MyWhenAll(Throw(1), Throw(2)).Wait();
}
catch (Exception ex) {
ex.Dump();
}
}
public async Task MyWhenAll(Task t1, Task t2) {
await Task.Delay(TimeSpan.FromMilliseconds(100));
try {
await Task.WhenAll(t1, t2);
}
catch {
throw new AggregateException(new[] { t1.Exception, t2.Exception });
}
}
public async Task Throw(int id) {
await Task.Delay(TimeSpan.FromMilliseconds(100));
throw new InvalidOperationException("Inner" + id);
}
For Task.WhenAll the exception is AggregateException with 2 inner exceptions.
For MyWhenAll the exception is AggregateException with one inner AggregateException with 2 inner exceptions.
Edit: Why I am doing this
I often need to call paging API:s and want to limit number of simultaneous connections.
The actual method signatures are
public static async Task<TResult[]> AsParallelAsync<TResult>(this IEnumerable<Task<TResult>> source, int maxParallel)
public static async Task<TResult[]> AsParallelUntilAsync<TResult>(this IEnumerable<Task<TResult>> source, int maxParallel, Func<Task<TResult>, bool> predicate)
It means I can do paging like this
var pagedRecords = await Enumerable.Range(1, int.MaxValue)
.Select(x => GetRecordsAsync(pageSize: 1000, pageNumber: x)
.AsParallelUntilAsync(maxParallel: 5, x => x.Result.Count < 1000);
var records = pagedRecords.SelectMany(x => x).ToList();
It all works fine, the aggregate within aggregate is just a minor inconvenience.
async methods are designed to only every set at most a single exception on the returned task, not multiple.
This leaves you with two options, you can either not use an async method to start with, instead relying on other means of performing your method:
public Task MyWhenAll(Task t1, Task t2)
{
return Task.Delay(TimeSpan.FromMilliseconds(100))
.ContinueWith(_ => Task.WhenAll(t1, t2))
.Unwrap();
}
If you have a more complex method that would be harder to write without using await, then you'll need to unwrap the nested aggregate exceptions, which is tedious, although not overly complex, to do:
public static Task UnwrapAggregateException(this Task taskToUnwrap)
{
var tcs = new TaskCompletionSource<bool>();
taskToUnwrap.ContinueWith(task =>
{
if (task.IsCanceled)
tcs.SetCanceled();
else if (task.IsFaulted)
{
if (task.Exception is AggregateException aggregateException)
tcs.SetException(Flatten(aggregateException));
else
tcs.SetException(task.Exception);
}
else //successful
tcs.SetResult(true);
});
IEnumerable<Exception> Flatten(AggregateException exception)
{
var stack = new Stack<AggregateException>();
stack.Push(exception);
while (stack.Any())
{
var next = stack.Pop();
foreach (Exception inner in next.InnerExceptions)
{
if (inner is AggregateException innerAggregate)
stack.Push(innerAggregate);
else
yield return inner;
}
}
}
return tcs.Task;
}
Use a TaskCompletionSource.
The outermost exception is created by .Wait() or .Result - this is documented as wrapping the exception stored inside the Task inside an AggregateException (to preserve its stack trace - this was introduced before ExceptionDispatchInfo was created).
However, Task can actually contain many exceptions. When this is the case, .Wait() and .Result will throw an AggregateException which contains multiple InnerExceptions. You can access this functionality through TaskCompletionSource.SetException(IEnumerable<Exception> exceptions).
So you do not want to create your own AggregateException. Set multiple exceptions on the Task, and let .Wait() and .Result create that AggregateException for you.
So:
var tcs = new TaskCompletionSource<object>();
tcs.SetException(new[] { t1.Exception, t2.Exception });
return tcs.Task;
Of course, if you then call await MyWhenAll(..) or MyWhenAll(..).GetAwaiter().GetResult(), then it will only throw the first exception. This matches the behaviour of Task.WhenAll.
This means you need to pass tcs.Task up as your method's return value, which means your method can't be async. You end up doing ugly things like this (adjusting the sample code from your question):
public static Task MyWhenAll(Task t1, Task t2)
{
var tcs = new TaskCompletionSource<object>();
var _ = Impl();
return tcs.Task;
async Task Impl()
{
await Task.Delay(10);
try
{
await Task.WhenAll(t1, t2);
tcs.SetResult(null);
}
catch
{
tcs.SetException(new[] { t1.Exception, t2.Exception });
}
}
}
At this point, though, I'd start to query why you're trying to do this, and why you can't use the Task returned from Task.WhenAll directly.
I deleted my previous answer, because I found a simpler solution. This solution does not involve the pesky ContinueWith method or the TaskCompletionSource type. The idea is to return a nested Task<Task> from a local function, and Unwrap() it from the outer container function. Here is a basic outline of this idea:
public Task<T[]> GetAllAsync<T>()
{
return LocalAsyncFunction().Unwrap();
async Task<Task<T[]>> LocalAsyncFunction()
{
var tasks = new List<Task<T>>();
// ...
await SomethingAsync();
// ...
Task<T[]> whenAll = Task.WhenAll(tasks);
return whenAll;
}
}
The GetAllAsync method is not async. It delegates all the work to the LocalAsyncFunction, which is async, and then Unwraps the resulting nested task and returns it. The unwrapped task contains in its .Exception.InnerExceptions property all the exceptions of the tasks, because it is just a facade of the internal Task.WhenAll task.
Let's demonstrate a more practical realization of this idea. The AsParallelUntilAsync method below enumerates lazily the source sequence and projects the items it contains to Task<TResult>s, until an item satisfies the predicate. It also limits the concurrency of the asynchronous operations. The difficulty is that enumerating the IEnumerable<TSource> could throw an exception too. The correct behavior in this case is to await all the running tasks before propagating the enumeration error, and return an AggregateException that contains both the enumeration error, and all the task errors that may have occurred in the meantime. Here is how it can be done:
public static Task<TResult[]> AsParallelUntilAsync<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, Task<TResult>> action,
Func<TSource, bool> predicate, int maxConcurrency)
{
return Implementation().Unwrap();
async Task<Task<TResult[]>> Implementation()
{
var tasks = new List<Task<TResult>>();
async Task<TResult> EnumerateAsync()
{
var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
using var enumerator = source.GetEnumerator();
while (true)
{
await semaphore.WaitAsync();
if (!enumerator.MoveNext()) break;
var item = enumerator.Current;
if (predicate(item)) break;
async Task<TResult> RunAndRelease(TSource item)
{
try { return await action(item); }
finally { semaphore.Release(); }
}
tasks.Add(RunAndRelease(item));
}
return default; // A dummy value that will never be returned
}
Task<TResult> enumerateTask = EnumerateAsync();
try
{
await enumerateTask; // Make sure that the enumeration succeeded
Task<TResult[]> whenAll = Task.WhenAll(tasks);
await whenAll; // Make sure that all the tasks succeeded
return whenAll;
}
catch
{
// Return a faulted task that contains ALL the errors!
return Task.WhenAll(tasks.Prepend(enumerateTask));
}
}
}
Related
Is it possible to use Async when using ForEach? Below is the code I am trying:
using (DataContext db = new DataLayer.DataContext())
{
db.Groups.ToList().ForEach(i => async {
await GetAdminsFromGroup(i.Gid);
});
}
I am getting the error:
The name 'Async' does not exist in the current context
The method the using statement is enclosed in is set to async.
List<T>.ForEach doesn't play particularly well with async (neither does LINQ-to-objects, for the same reasons).
In this case, I recommend projecting each element into an asynchronous operation, and you can then (asynchronously) wait for them all to complete.
using (DataContext db = new DataLayer.DataContext())
{
var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
var results = await Task.WhenAll(tasks);
}
The benefits of this approach over giving an async delegate to ForEach are:
Error handling is more proper. Exceptions from async void cannot be caught with catch; this approach will propagate exceptions at the await Task.WhenAll line, allowing natural exception handling.
You know that the tasks are complete at the end of this method, since it does an await Task.WhenAll. If you use async void, you cannot easily tell when the operations have completed.
This approach has a natural syntax for retrieving the results. GetAdminsFromGroupAsync sounds like it's an operation that produces a result (the admins), and such code is more natural if such operations can return their results rather than setting a value as a side effect.
This little extension method should give you exception-safe async iteration:
public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
foreach (var value in list)
{
await func(value);
}
}
Since we're changing the return type of the lambda from void to Task, exceptions will propagate up correctly. This will allow you to write something like this in practice:
await db.Groups.ToList().ForEachAsync(async i => {
await GetAdminsFromGroup(i.Gid);
});
Starting with C# 8.0, you can create and consume streams asynchronously.
private async void button1_Click(object sender, EventArgs e)
{
IAsyncEnumerable<int> enumerable = GenerateSequence();
await foreach (var i in enumerable)
{
Debug.WriteLine(i);
}
}
public static async IAsyncEnumerable<int> GenerateSequence()
{
for (int i = 0; i < 20; i++)
{
await Task.Delay(100);
yield return i;
}
}
More
The simple answer is to use the foreach keyword instead of the ForEach() method of List().
using (DataContext db = new DataLayer.DataContext())
{
foreach(var i in db.Groups)
{
await GetAdminsFromGroup(i.Gid);
}
}
Here is an actual working version of the above async foreach variants with sequential processing:
public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
foreach (var item in enumerable)
await Task.Run(() => { action(item); }).ConfigureAwait(false);
}
Here is the implementation:
public async void SequentialAsync()
{
var list = new List<Action>();
Action action1 = () => {
//do stuff 1
};
Action action2 = () => {
//do stuff 2
};
list.Add(action1);
list.Add(action2);
await list.ForEachAsync();
}
What's the key difference? .ConfigureAwait(false); which keeps the context of main thread while async sequential processing of each task.
This is not an old question, but .Net 6 introduced Parallel.ForeachAsync:
var collectionToIterate = db.Groups.ToList();
await Parallel.ForEachAsync(collectionToIterate, async (i, token) =>
{
await GetAdminsFromGroup(i);
});
ForeachAsync also accepts a ParallelOptions object, but usually you don't want to mess with the MaxDegreeOfParallelism property:
ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 };
var collectionToIterate = db.Groups.ToList();
await Parallel.ForEachAsync(collectionToIterate, parallelOptions , async (i, token) =>
{
await GetAdminsFromGroup(i);
});
From Microsoft Docs: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism?view=net-6.0
By default, For and ForEach will utilize however many threads the underlying scheduler provides, so changing MaxDegreeOfParallelism from the default only limits how many concurrent tasks will be used.
Generally, you do not need to modify this setting....
Add this extension method
public static class ForEachAsyncExtension
{
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current).ConfigureAwait(false);
}));
}
}
And then use like so:
Task.Run(async () =>
{
var s3 = new AmazonS3Client(Config.Instance.Aws.Credentials, Config.Instance.Aws.RegionEndpoint);
var buckets = await s3.ListBucketsAsync();
foreach (var s3Bucket in buckets.Buckets)
{
if (s3Bucket.BucketName.StartsWith("mybucket-"))
{
log.Information("Bucket => {BucketName}", s3Bucket.BucketName);
ListObjectsResponse objects;
try
{
objects = await s3.ListObjectsAsync(s3Bucket.BucketName);
}
catch
{
log.Error("Error getting objects. Bucket => {BucketName}", s3Bucket.BucketName);
continue;
}
// ForEachAsync (4 is how many tasks you want to run in parallel)
await objects.S3Objects.ForEachAsync(4, async s3Object =>
{
try
{
log.Information("Bucket => {BucketName} => {Key}", s3Bucket.BucketName, s3Object.Key);
await s3.DeleteObjectAsync(s3Bucket.BucketName, s3Object.Key);
}
catch
{
log.Error("Error deleting bucket {BucketName} object {Key}", s3Bucket.BucketName, s3Object.Key);
}
});
try
{
await s3.DeleteBucketAsync(s3Bucket.BucketName);
}
catch
{
log.Error("Error deleting bucket {BucketName}", s3Bucket.BucketName);
}
}
}
}).Wait();
If you are using EntityFramework.Core there is an extension method ForEachAsync.
The example usage looks like this:
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
public class Example
{
private readonly DbContext _dbContext;
public Example(DbContext dbContext)
{
_dbContext = dbContext;
}
public async void LogicMethod()
{
await _dbContext.Set<dbTable>().ForEachAsync(async x =>
{
//logic
await AsyncTask(x);
});
}
public async Task<bool> AsyncTask(object x)
{
//other logic
return await Task.FromResult<bool>(true);
}
}
I would like to add that there is a Parallel class with ForEach function built in that can be used for this purpose.
The problem was that the async keyword needs to appear before the lambda, not before the body:
db.Groups.ToList().ForEach(async (i) => {
await GetAdminsFromGroup(i.Gid);
});
This is method I created to handle async scenarios with ForEach.
If one of tasks fails then other tasks will continue their execution.
You have ability to add function that will be executed on every exception.
Exceptions are being collected as aggregateException at the end and are available for you.
Can handle CancellationToken
public static class ParallelExecutor
{
/// <summary>
/// Executes asynchronously given function on all elements of given enumerable with task count restriction.
/// Executor will continue starting new tasks even if one of the tasks throws. If at least one of the tasks throwed exception then <see cref="AggregateException"/> is throwed at the end of the method run.
/// </summary>
/// <typeparam name="T">Type of elements in enumerable</typeparam>
/// <param name="maxTaskCount">The maximum task count.</param>
/// <param name="enumerable">The enumerable.</param>
/// <param name="asyncFunc">asynchronous function that will be executed on every element of the enumerable. MUST be thread safe.</param>
/// <param name="onException">Acton that will be executed on every exception that would be thrown by asyncFunc. CAN be thread unsafe.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public static async Task ForEachAsync<T>(int maxTaskCount, IEnumerable<T> enumerable, Func<T, Task> asyncFunc, Action<Exception> onException = null, CancellationToken cancellationToken = default)
{
using var semaphore = new SemaphoreSlim(initialCount: maxTaskCount, maxCount: maxTaskCount);
// This `lockObject` is used only in `catch { }` block.
object lockObject = new object();
var exceptions = new List<Exception>();
var tasks = new Task[enumerable.Count()];
int i = 0;
try
{
foreach (var t in enumerable)
{
await semaphore.WaitAsync(cancellationToken);
tasks[i++] = Task.Run(
async () =>
{
try
{
await asyncFunc(t);
}
catch (Exception e)
{
if (onException != null)
{
lock (lockObject)
{
onException.Invoke(e);
}
}
// This exception will be swallowed here but it will be collected at the end of ForEachAsync method in order to generate AggregateException.
throw;
}
finally
{
semaphore.Release();
}
}, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
break;
}
}
}
catch (OperationCanceledException e)
{
exceptions.Add(e);
}
foreach (var t in tasks)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
// Exception handling in this case is actually pretty fast.
// https://gist.github.com/shoter/d943500eda37c7d99461ce3dace42141
try
{
await t;
}
#pragma warning disable CA1031 // Do not catch general exception types - we want to throw that exception later as aggregate exception. Nothing wrong here.
catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
{
exceptions.Add(e);
}
}
if (exceptions.Any())
{
throw new AggregateException(exceptions);
}
}
}
I've made a queue, which contains tasks to do. After creating some tasks manually with new Task() in Returns method, my whole application hangs - await current;. The body of the task is not even triggered.
ConfigureAwait(false) didn't help.
The first task in the queue, which is not created by me, but other framework is executing successfully and returning a value. Mine - doesn't. I've tried add Task.CompletedTask and then it has worked. I don't understand why I can't even reach the body of the task containing _output assignment.
IDE debugger code screenshot
---UPDATE---
The code works when I use code below. With await it doesn't. Any ideas?
current.Start();
current.Wait();
Original code
private readonly Queue<Task> _pipe;
public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
_pipe.Enqueue(new Task(() => // this task causes a problem and breakpoint isn't hit inside
{
_output = outputBuilder(_results);
}));
return this;
}
public async Task<TOutput> Execute()
{
Task current;
while (_pipe.TryDequeue(out current))
{
if (current.IsCommandExecution())
{
IExecutionResult result = await (Task<IExecutionResult>)current; // this awaits successfully
_results.Add(result);
}
else
{
await current; // hangs here
}
}
return await Task.FromResult(_output);
}
Usage
[HttpGet("eventflow/pipe/issue/add/{title}")]
public async Task<IActionResult> PipeAction(string title)
=> Ok(
await Pipe<IExecutionResult>()
.Validate(title)
.Handle<AddIssueCommand>(IssueId.New, title)
.Returns(results => results.First())
.Execute());
You should never use the Task constructor. This goes double on ASP.NET, since constructed tasks are always Delegate Tasks, which interfere with the ASP.NET usage of the thread pool. The actual reason that the await hangs is because manually-created tasks need to be started.
If you have synchronous work that you need to wrap into a Task to work alongside asynchronous tasks, then you should use Task.CompletedTask and Task.FromException:
private static Task SynchronousWork(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
try { _output = outputBuilder(_results); return Task.CompletedTask; }
catch (Exception ex) { return Task.FromException(ex); }
}
public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
_pipe.Enqueue(SynchronousWork(outputBuilder));
return this;
}
However, note that this executes outputBuilder immediately, which may not be desirable due to its side effects on _results and _output. If you want a delayed execution queue, then the type in the queue needs to be changed from Task to Func<Task>. Then you can add to it as such:
public IPipeBuilder<TOutput> Returns(Func<IEnumerable<IExecutionResult>, TOutput> outputBuilder)
{
_pipe.Enqueue(() =>
{
try { _output = outputBuilder(_results); return Task.CompletedTask; }
catch (Exception ex) { return Task.FromException(ex); }
});
return this;
}
and you would consume it by calling each delegate one at a time and inspecting the task it returns:
public async Task<TOutput> Execute()
{
while (_pipe.TryDequeue(out var currentFunc))
{
var currentTask = currentFunc();
if (currentTask.IsCommandExecution())
{
IExecutionResult result = await (Task<IExecutionResult>)currentTask;
_results.Add(result);
}
else
{
await currentTask;
}
}
return _output;
}
Okay, thank you. I've ended up with such class and Queue<Func<Task>> like you said.
public sealed class SyncTaskWrapper
{
private Func<Task> _action;
public SyncTaskWrapper(Action action)
=> _action = CreateFunc(action);
private static Func<Task> CreateFunc(Action action)
=> () =>
{
try
{
action();
return Task.CompletedTask;
}
catch (Exception exception)
{
return Task.FromException(exception);
}
};
public static implicit operator Func<Task>(SyncTaskWrapper #this)
=> #this._action;
}
with usage
_pipe.Enqueue(new SyncTaskWrapper(() =>
_output = outputBuilder(_results)));
I would say the following two code snippets I have are equivalent, but they aren't.
The following is working correctly:
var entry3 = Task.Run(async () => await entry2.GetMemberGroupsAsync(false)).WaitForResult().FirstOrDefault();
The following code, where I just moved the Task.Run.WaitForResult chain into an extension method, isn't working, but produces a deadlock:
var entry3 = entry2.GetMemberGroupsAsync(false).RunSynchronouslyAndReturnResult().FirstOrDefault();
public static T RunSynchronouslyAndReturnResult<T>(this Task<T> task)
{
return Task.Run(async () => await task).WaitForResult();
}
Why aren't these two code snippets equivalent?
For completeness's sake, the GetMemberGroupsAsync method is provided by Microsoft Azure Graph API, and the function WaitForResult is defined below. As far as I can see, it doesn't do anything different depending on the caller name or sth. like that:
public static TResult WaitForResult<TResult>(this Task<TResult> task,
bool continueOnCapturedContext = false)
{
if (task == null)
{
throw new ArgumentNullException("task");
}
try
{
return PreventForDeadLocks(task, continueOnCapturedContext).Result;
}
catch (AggregateException ex)
{
if (ex.InnerExceptions.Count == 1)
{
throw ex.InnerExceptions[0];
}
throw;
}
}
public static async Task<TResult> PreventForDeadLocks<TResult>(this Task<TResult> task,
bool continueOnCapturedContext = false)
{
return await task.ConfigureAwait(continueOnCapturedContext: continueOnCapturedContext);
}
The difference here is in which synchronization context your task started. Here:
var entry3 = Task.Run(async () => await entry2.GetMemberGroupsAsync(false)).WaitForResult().FirstOrDefault();
you start your async task (I mean await entry2.GetMemberGroupsAsync(false)) inside Task.Run call, so UI synchronization context is not captured. But here:
var entry3 = entry2.GetMemberGroupsAsync(false).RunSynchronouslyAndReturnResult().FirstOrDefault();
You implicitly start your task (entry2.GetMemberGroupsAsync(false) returns Task) on UI context, so UI synchronization context is captured, and you have your deadlock.
In the first case, GetMemberGroupsAsync is called on a different thread than WaitForResult.
In the second case, it is called on the same thread as WaitForResult. You're just awaiting on a different thread.
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;
}
I read a few threads about TaskCancellations.. However, I cannot find a solution for a simple question: How do I get a default value when my task fails?
I cannot (!) modify the task itself and put a try catch wrapper around it. I could of course put a try-catch around await, but I would like to handle this with ContinueWith - if possible.
public Task<List<string>> LoadExample()
{
Task<List<string>> task = LoadMyExampleTask();
task.ContinueWith(t => default(List<string>), TaskContinuationOptions.OnlyOnFaulted);
return task;
}
I thought this would be the correct way to deal with the problem. However, my application throws a JsonParseException (which is called in LoadMyExampleTask). I would expect to get null or (even better) an empty list.
In fact, all I want is:
var emptyOrFilledList = await LoadExample(); // guaranteed no exception thrown
Based on Luaan's great answer I wrote an extension method with a defaultValue-option:
public static Task<T> DefaultIfFaulted<T>(this Task<T> #this, T defaultValue = default(T))
{
return #this.ContinueWith(t => t.IsCompleted ? t.Result : defaultValue);
}
Edit: await myTask.DefaultifFaulted() just throwed a
[ERROR] FATAL UNHANDLED EXCEPTION: System.AggregateException
Are you sure that every exception is caught?
If you want that, you must not return the original task - you need to return the continuation.
public Task<List<string>> LoadExample()
{
Task<List<string>> task = LoadMyExampleTask();
return task.ContinueWith(t =>
t.IsFaulted || t.IsCanceled ? default(List<string>) : t.Result);
}
Your original code did allow the continuation to run when the original task faulted, but you didn't read the status of that task - the fact that a task has a continuation which handles errors is entirely irrelevant to what an await on the original task will do.
Of course, it's rather easy to make this into a generic helper method:
public static Task<T> DefaultIfFaulted<T>(this Task<T> #this)
{
return #this.ContinueWith (t => t.IsCanceled || t.IsFaulted ? default(T) : t.Result);
}
As promised, here are the DefaultIfFaulted<T> variants which are true to their name (and the title of this question). They preserve the antecedent task's behavior unless it's faulted (specifically, cancellation is propagated rather than ignored or masked by an AggregateException):
Old-school (.NET 4.0) way:
public static Task<T> DefaultIfFaulted<T>(this Task<T> task)
{
// The continuation simply returns the antecedent task unless it's faulted.
Task<Task<T>> continuation = task.ContinueWith(
t => (t.Status == TaskStatus.Faulted) ? Task.FromResult(default(T)) : t,
TaskContinuationOptions.ExecuteSynchronously
);
return continuation.Unwrap();
}
Async/await way (simple but slower):
public static async Task<T> DefaultIfFaulted<T>(this Task<T> task)
{
try
{
return await task.ConfigureAwait(false);
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
return default(T);
}
}
Async/await way (perf almost identical to Unwrap):
public static async Task<T> DefaultIfFaulted<T>(this Task<T> task)
{
// Await completion regardless of resulting Status (alternatively you can use try/catch).
await task
.ContinueWith(_ => { }, TaskContinuationOptions.ExecuteSynchronously)
.ConfigureAwait(false);
return task.Status != TaskStatus.Faulted
// This await preserves the task's behaviour
// in all cases other than faulted.
? await task.ConfigureAwait(continueOnCapturedContext: false)
: default(T);
}
Tests (passed by all of the above):
using Xunit;
[Fact]
public async Task DefaultIfFaultedTest()
{
var success = Task.Run(() => 42);
var faulted = Task.Run(new Func<int>(() => { throw new InvalidOperationException(); }));
Assert.Equal(42, await success.DefaultIfFaulted());
Assert.Equal(0, await faulted.DefaultIfFaulted());
await Assert.ThrowsAsync<TaskCanceledException>(() =>
{
var tcs = new TaskCompletionSource<int>();
tcs.SetCanceled();
return tcs.Task.DefaultIfFaulted();
});
}