I have a method, GetSomethingAsync, that is return Task<MyTypeA>.Run(() => GetSomething());
I have another method, GetSomethingElseAsync, that is return Task<MyTypeB>.Run(() => GetSomethingElse());
I want the second task to run conditionally, based on something from the first, so I have
var task1 = GetSomethingAsync();
var task2 = task1.ContinueWith(x =>
x.Result == null ? Task.FromResult(null) : GetSomethingElseAsync());
task2 compiles as a Task<Task<MyTypeB>>. I was expecting Task<MyTypeB>. Is it possible to get my expected result?
Assuming
var task1 = GetSomethingAsync();
is defined with async and returns a Task<T>.
And the signature of ContinueWith() is
public Task<TResult> ContinueWith<TResult>(
Func<Task, TResult> continuationFunction
)
Now focusing on your lambda result:
x => x.Result == null ? Task.FromResult(null)
Reads something like, if the Result of the task is null then return a Task (Task.FromResult).
So your Func<Task, TResult> the TResult is a Task<T> as if the lambda was written as:
Task<T> AnonymousFunction(task x)
{
return ... Task.FromResult(null);
}
Now the result of a ContinueWith() is a Task<TResult> and since we've determine a TResult is a Task<T> then the result type is Task<Task<T>>.
What you need is the Unwrap extension method. This method takes a nested task Task<Task<T>>, and flattens it to a Task<T>. The unwrapped task represents the completion of both the outer and inner task. If anyone of them fail, the unwrapped task contains the Exception that occurred in either task:
Task<MyTypeA> task1 = GetSomethingAsync();
Task<MyTypeB> task2 = task1.ContinueWith(t =>
{
return t.Result is null ?
Task.FromResult<MyTypeB>(null) : GetSomethingElseAsync();
}).Unwrap();
Unfortunately the above code is far from perfect.
It violates the guideline CA2008: Do not create tasks without passing a TaskScheduler.
It propagates the cancellation of the task1 as a failure. So if the task1.IsCanceled, the task2 will be IsFaulted.
It wraps a possible exception of task1 in a nested AggregateException.
Here is the same code corrected:
Task<MyTypeA> task1 = GetSomethingAsync();
Task<MyTypeB> task2 = task1.ContinueWith(t =>
{
if (t.IsFaulted)
return Task.FromException<MyTypeB>(t.Exception.InnerException);
if (t.IsCanceled)
{
TaskCompletionSource<MyTypeB> tcs = new();
tcs.SetCanceled(new TaskCanceledException(t).CancellationToken);
return tcs.Task;
}
return t.Result is null ?
Task.FromResult<MyTypeB>(null) : GetSomethingElseAsync();
}, TaskScheduler.Default).Unwrap();
A simpler solution is to ditch the cumbersome ContinueWith method, in favor of the handy async/await language feature:
Task<MyTypeA> task1 = GetSomethingAsync();
Task<MyTypeB> task2 = ((Func<Task<MyTypeB>>)(async () =>
{
MyTypeA result = await task1.ConfigureAwait(false);
return result is null ? null :
await GetSomethingElseAsync().ConfigureAwait(false);
}))();
This can be simplified even further by using the Then family of extension methods that can be found here (by Stephen Toub).
Task<MyTypeA> task1 = GetSomethingAsync();
Task<MyTypeB> task2 = task1.Then(async result =>
result is null ? null : await GetSomethingElseAsync().ConfigureAwait(false));
You can think the Then extension method as a ContinueWithResult, with an argument that contains the result of the antecedent task, instead of the antecedent task itself.
I believe this is what you are looking for:
var task2 = task1.ContinueWith(completedTask => completedTask.Result == null ? (TypeB)null : GetSomethingElse());
task2 should now be of type Task<MyTypeB>.
The issue is that task1.ContinueWith already returns a Task<T>. In your statement, you are also returning a Task<T>, or more accurately, a Task<MyTypeB>. Hence, the result of the call to task1.ContinueWith is Task<T> where T is Task<MyTypeB>. What you see in the debugger: Task<Task<MyTypeB>>.
Related
The first function is designed to enable linq to execute lambda functions safely in parallel (even the async void ones).
So you can do collection.AsParallel().ForAllASync(async x => await x.Action).
The second function is designed to enable you to combine and execute multiple IAsyncEnumerables in parallel and return their results as quick as possible.
I have the following code:
public static async Task ForAllAsync<TSource>(
this ParallelQuery<TSource> source,
Func<TSource, Task> selector,
int? maxDegreeOfParallelism = null)
{
int maxAsyncThreadCount = maxDegreeOfParallelism ?? Math.Min(System.Environment.ProcessorCount, 128);
using SemaphoreSlim throttler = new SemaphoreSlim(maxAsyncThreadCount, maxAsyncThreadCount);
IEnumerable<Task> tasks = source.Select(async input =>
{
await throttler.WaitAsync().ConfigureAwait(false);
try
{
await selector(input).ConfigureAwait(false);
}
finally
{
throttler.Release();
}
});
await Task.WhenAll(tasks).ConfigureAwait(true);
}
public static async IAsyncEnumerable<T> ForAllAsync<TSource, T>(
this ParallelQuery<TSource> source,
Func<TSource, IAsyncEnumerable<T>> selector,
int? maxDegreeOfParallelism = null,
[EnumeratorCancellation]CancellationToken cancellationToken = default)
where T : new()
{
IEnumerable<(IAsyncEnumerator<T>, bool)> enumerators =
source.Select(x => (selector.Invoke(x).GetAsyncEnumerator(cancellationToken), true)).ToList();
while (enumerators.Any())
{
await enumerators.AsParallel()
.ForAllAsync(async e => e.Item2 = (await e.Item1.MoveNextAsync()), maxDegreeOfParallelism)
.ConfigureAwait(false);
foreach (var enumerator in enumerators)
{
yield return enumerator.Item1.Current;
}
enumerators = enumerators.Where(e => e.Item2);
}
}
If I remove the "ToList()" from the second function, yield return starts to return null as enumerator.Item1.Current tends to be null, despite enumerator.Item2 (the result from MoveNextAsync()) being true.
Why?
This is a classic case of deferred execution. Every time you invoke an evaluating method on a non-materialized IEnumerable<>, it does the work to materialize the IEnumerable. In this case that's re-invoking your selector and creating new instances of the tasks that await the GetAsyncEnumerator calls.
With the call to .ToList() you materialize the IEnumerable. Without it, materialization occurs with with every call to .Any(), the call to ForAllAsync(), and at your foreach loop.
The same behavior can be reproduced minimally like this:
var enumerable = new[] { 1 }.Select(_ => Task.Delay(10));
await Task.WhenAll(enumerable);
Console.WriteLine(enumerable.First().IsCompleted); // False
enumerable = enumerable.ToList();
await Task.WhenAll(enumerable);
Console.WriteLine(enumerable.First().IsCompleted); // True
In the first call to enumerable.First(), we end up with a different task instance than the one that we awaited in the line before it.
In the second call, we're using the same instance because the Task was already materialized into a List.
I am passing an async delegate to the LINQ Select method, and I would prefer to get a list of ValueTasks instead of a list of Tasks. How can I do it? Example:
var result = (new[] { 0 }).Select(async x => await Task.Yield()).ToArray();
Console.WriteLine($"Result type: {result.GetType()}");
Result type: System.Threading.Tasks.Task[]
This is not desirable. I figured out that I can create the list I want by replacing the async delegate with an async method, like this:
var result = (new[] { 0 }).Select(DoAsync).ToArray();
Console.WriteLine($"Result type: {result.GetType()}");
async ValueTask DoAsync(int arg)
{
await Task.Yield();
}
Result type: System.Threading.Tasks.ValueTask[]
This works but it's awkward. Is there any way to keep the neat delegate syntax, and still get the ValueTasks I want?
You can explicitly write value task like this
var result = (new[] { 0 }).Select<int, ValueTask>(async x => await Task.Yield()).ToArray();
I am getting an error with the following code.
public async Task<bool> FlushUrlAsync(Uri url, bool recursive, CancellationToken token = default(CancellationToken))
{
_serverPortsConfig.CacheServerPorts
.Select(cacheServerPort => $"http://{url.Host}:{cacheServerPort}{url.AbsolutePath}")
.Aggregate(true, (current, memoryCacheUrl) => current && await FlushUrlAsync(recursive, memoryCacheUrl)); //<--- produces the next error:
// Cannot convert async lambda expression to delegate type
// 'Func<bool, string, bool>'. An async lambda expression may return
// void, Task or Task<T>, none of which are convertible to
// 'Func<bool, string, bool>'.
}
This method calls the following function
private async Task<bool> FlushUrlAsync(bool recursive, string memoryCacheUrl)
{
return await someMagic(); //for clearity I removed the code.
}
It looks a lot like: Convert async lambda expression to delegate type System.Func<T>?, howevery, there solution won't /can't get it to work for me.
I had:
var result = true;
foreach (var cacheServerPort in _serverPortsConfig.CacheServerPorts)
{
var memoryCacheUrl = $"http://{url.Host}:{cacheServerPort}{url.AbsolutePath}";
result = result && await FlushUrlAsync(memoryCacheUrl, recursive);
}
return result;
Then resharper gave me the code that is provided, but just adding the async keyword doesn't work.
.Aggregate(true, async (current, memoryCacheUrl) => current && await FlushUrlAsync(recursive, memoryCacheUrl));
will give me the erorr: the return type of async method must be void, task or Task.
any ideas?
I would personally use the foreach implementation, but answering the concrete question.
Without async the Aggregate overload used has the following signature:
bool Aggregate<bool, string>(bool seed, Func<bool, string, bool> func)
Note the func parameter - it's a method receiving bool and string returning a bool. The important part is that the type of the first argument is the same as the type of the result, as well as the type of seed parameter.
Since async lambda must return a Task derived object. You need a bool result, so let substitute it with Task<bool>:
Task<bool> Aggregate<bool, string>(Task<bool> seed, Func<Task<bool>, string, Task<bool>> func)
which leads to the following solution:
return await _serverPortsConfig.CacheServerPorts
.Select(cacheServerPort => $"http://{url.Host}:{cacheServerPort}{url.AbsolutePath}")
.Aggregate(Task.FromResult(true), async (current, memoryCacheUrl) =>
await current && await FlushUrlAsync(recursive, memoryCacheUrl));
Use ContinueWith with TaskContinuationOptions,
Func<Exception, bool> handlerFunc = (Exception ex) =>
{
ex.HandleExceptionAsync().ContinueWith(async (result) =>
{
// all await operations can be called here
}, System.Threading.Tasks.TaskContinuationOptions.OnlyOnRanToCompletion);
return true;
};
Can someone please explain the difference between these two statements:
Task<Task> bTask = backup.BackupCurrentDatabaseAsync()
.ContinueWith(_ => CompressArchiveAsync());
//unwrap the tasks to produce one entire task
Task t = bTask.Unwrap();
vs
Task<Task> bTask = backup.BackupCurrentDatabaseAsync()
.ContinueWith(_ =>
{
CompressArchiveAsync();
});
//unwrap the tasks to produce one entire task
Task t = bTask.Unwrap();
The methodsExtractArchiveAsync(), BackupCurrentDatabaseAsync(), RestoreDatabaseAsync() all return a Task.
Here, the first Continuation returns a Task<Task>. I can then Unwrap() this task to put Continuations on the resultant (inner) task.
The second version doesn't compile. The only different here is the braces around the CompressArchiveAsync().
I am trying to access the resultant (internal) Task to check the Task.Status. If I use the second method, the Task.Status is reporting the result of the BackupCurrentDatabaseAsync() task.
.ContinueWith(_ => CompressArchiveAsync());
is equivalent to:
.ContinueWith(_ =>
{
return CompressArchiveAsync();
});
Notice the return.
Your second code snippet doesn't compile because ContinueWith doesn't return a Task<Task>, but simply a Task, and there's nothing to unwrap.
The following is bound to a Func<Task, Task> (a function that takes a Task and returns a Task)
_ =>
{
return CompressArchiveAsync();
}
But the following is actually bound to an Action<Task> (a function that takes a Task but doesn't return anything):
_ =>
{
CompressArchiveAsync();
}
And the reference to the Task created by CompressArchiveAsync is never returned. Without a reference to it, you can't check the Task's status.
Note that:
ContinueWith<TResult>(Func<Task, TResult>) returns a Task<TResult>
ContinueWith(Action<Task>) returns a Task.
Therefore your ContinueWith(Func<Task, Task>) returns a Task<Task> that you can unwrap, but your ContinueWith(Action<Task>) simply returns a Task.
The difference is in the Lambda Expression syntax.
There are 2 types of Lambdas: Expression Lambdas and Statement Lambdas. Expression Lambdas have no braces and return the result of the expression while Statement Lambdas have braces containing zero or more statements (one of them can be a return statement).
So this Expression Lambda:
_ => CompressArchiveAsync()
Is equivalent to this Statement Lambda:
_ => { return CompressArchiveAsync(); }
So, the difference is that in the first continuation you are returning a task but in the second you are not, it's just a void anonymous delegate. That's why the first continuation is a Task<Task> while the second is just a Task.
Long comment to show 4.5 code.
If you could move to .Net 4.5 than code you are trying to write can be rewritten in more compact way with async/await which is essentially implements all that code internally:
async Task CompleteBackup()
{
await backup.BackupCurrentDatabaseAsync()
await CompressArchiveAsync());
await .....
}
In the first example, you are calling ContinueWith with a Func. Therefore, it will return a Task<T>. The second try will call the ContinueWith overload with an Action, because... well it's an action, it does not return anything. So it will return a simple Task without T.
I want to combine the result of 2 tasks in one List collection.
Make sure that- I want to run both methods in parallel.
Code:
List<Employee> totalEmployees = new List<Employee>();
Method1:
public async Task<IEnumerable<Employee>> SearchEmployeeFromDb();
Method2:
public async Task<IEnumerable<Employee>> GetEmployeeFromService();
Now, I want to hold the result of these two methods in totalEmployees field, also these 2 method should run asynchronously.
While many answers are close, the cleanest and most efficient option is using Task.WhenAll combined with SelectMany:
async Task<IEnumerable<Employee>> Combine()
{
var results = await Task.WhenAll(SearchEmployeeFromDb(), GetEmployeeFromService());
return results.SelectMany(result => result);
}
This assumes that by parallel you mean concurrently. If you wish to run these operations with multiple threads from the beginning (including the synchronous parts of the async method) you need to also use Task.Run to offload work to a ThreadPool thread:
private async Task<IEnumerable<Employee>> Combine()
{
var results =
await Task.WhenAll(Task.Run(() => SearchEmployeeFromDb()), Task.Run(() => GetEmployeeFromService()));
return results.SelectMany(result => result);
}
Start both tasks
Use Task.WhenAll to wait for both tasks to finish
Use Enumerable.Concat to combine the results
var searchEmployeesTask = SearchEmployeeFromDb();
var getEmployeesTask = GetEmployeeFromService();
await Task.WhenAll(searchEmployeesTask, getEmployeesTask);
var totalEmployees = searchEmployeesTask.Result.Concat(getEmployeesTask.Result);
You can use Task.WhenAll to create a task which will return when all supplied tasks are complete
var result = await Task.WhenAll(SearchEmployeeFromDb(),GetEmployeeFromService());
var combined = result[0].Concat(result[1]);
Something like this should work:
var t1 = SearchEmployeeFromDb()
var t2 = GetEmployeeFromService()
await Task.WhenAll(t1, t2)
// Now use t1.Result and t2.Result to get `totalEmployees`
Use ConfigureAwait(false) to avoid deadlocking, define the tasks, execute and then await.
var fromDbTask = SearchEmployeeFromDb().ConfigureAwait(false);
var fromServiceTask = GetEmployeeFromService().ConfigureAwait(false);
var fromDbResult = await fromDbTask;
var totalEmployees = new List(fromDbResult);
var fromServiceResult = await fromServiceResult;
totalEmployees.AddRange(fromServiceResult);
... or use whichever way you want to merge the two lists.
I updated the solution, it was unneccessary to create the list and then append the first result. We wait for the first method to finish and then create the list.