Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where is the OnCompleted callback for Task?

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?

like image 739
Serj Sagan Avatar asked Aug 31 '16 01:08

Serj Sagan


3 Answers

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);
}
like image 51
Stephen Cleary Avatar answered Oct 17 '22 12:10

Stephen Cleary


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.

like image 2
Siraj Mansour Avatar answered Oct 17 '22 11:10

Siraj Mansour


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.

like image 1
Theodor Zoulias Avatar answered Oct 17 '22 11:10

Theodor Zoulias