I'd like to be be able to do this:
class MyClass
{
IRepo repo;
public MyClass(IMyRepo repo) { this.repo = repo; }
long ID { get; set; }
string Prop1 { get; set; }
string Prop2 { get; set; }
public async Task LoadAsync()
{
await Task.WhenAll(
repo.GetProp1ByIDAsync(ID).OnComplete(x => Prop1 = x),
repo.GetProp2ByIDAsync(ID).OnComplete(x => Prop2 = x)
);
}
}
Of course, I can't seem to find any standard library that has this OnComplete
extension for Task
. Do I really need to create my own or is there a library that already has this extension method? I see that there's ContinueWith
but that does not give me the unwrapped result, I still have to await
it or .Result
it... so wouldn't that block the thread? Holding up the second repo
call until this completed? If it doesn't hold it up then why am I getting that result still wrapped, would it be cleaner to return the unwrapped result to me? Or am I missing something?
I can't seem to find any standard library that has this OnComplete extension for Task. Do I really need to create my own or is there a library that already has this extension method? I see that there's ContinueWith but that does not give me the unwrapped result, I still have to await it or .Result it... so wouldn't that block the thread?
ContinueWith
is the method you're looking for; Result
won't block on the task because it's already completed by the time the callback is invoked.
However, ContinueWith
is a dangerous, low-level API. You should use await
instead:
public async Task LoadAsync()
{
await Task.WhenAll(
LoadProp1Async(),
LoadProp2Async()
);
}
private async Task LoadProp1Async()
{
Prop1 = await repo.GetProp1ByIDAsync(ID);
}
private async Task LoadProp2Async()
{
Prop2 = await repo.GetProp2ByIDAsync(ID);
}
First you should go for await Task.WhenAll(...)
instead of .WaitAll
.
ContinueWith
is what you need to fire up another task on completion. Calling .Result
on the unwrapped result passed to the ContinueWith
would return instantly as the task has already completed, same goes if you await it.
Calling .ContinueWith
on the first task has no effect on the second one, and as you are awaiting them later using await Task.WhenAll(...)
they would execute in paralell.
Checkout this link with the ReadFileAsync
example.
Here are the extension methods you are looking for:
public static async Task OnCompletedSuccessfully(this Task task, Action continuation,
bool continueOnCapturedContext = true)
{
await task.ConfigureAwait(continueOnCapturedContext);
continuation();
}
public static async Task OnCompletedSuccessfully(this Task task, Func<Task> continuation,
bool continueOnCapturedContext = true)
{
await task.ConfigureAwait(continueOnCapturedContext);
await continuation().ConfigureAwait(false);
}
public static async Task OnCompletedSuccessfully<TResult>(
this Task<TResult> task, Action<TResult> continuation,
bool continueOnCapturedContext = true)
{
var result = await task.ConfigureAwait(continueOnCapturedContext);
continuation(result);
}
public static async Task OnCompletedSuccessfully<TResult>(
this Task<TResult> task, Func<TResult, Task> continuation,
bool continueOnCapturedContext = true)
{
var result = await task.ConfigureAwait(continueOnCapturedContext);
await continuation(result).ConfigureAwait(false);
}
public static async Task<TNewResult> OnCompletedSuccessfully<TResult, TNewResult>(
this Task<TResult> task, Func<TResult, TNewResult> continuation,
bool continueOnCapturedContext = true)
{
var result = await task.ConfigureAwait(continueOnCapturedContext);
return continuation(result);
}
public static async Task<TNewResult> OnCompletedSuccessfully<TResult, TNewResult>(
this Task<TResult> task, Func<TResult, Task<TNewResult>> continuation,
bool continueOnCapturedContext = true)
{
var result = await task.ConfigureAwait(continueOnCapturedContext);
return await continuation(result).ConfigureAwait(false);
}
I renamed OnCompleted
to OnCompletedSuccessfully
, because the continuation will run only if the task will complete successfully. The good news are that there are overloads that understand async delegates, so you won't need to unwrap anything. The bad news are that in case of an exception you'll get the exception back, but you won't be able to differentiate between a faulted task and a faulted continuation. Which is far from ideal. Especially since you intend to chain this method to the original task, and you may not have a variable pointing to the original task so that you can investigate its properties.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With