Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reacting to Task completion: `.ContinueWith()` vs `GetAwaiter().OnCompleted()`

Say I have a Task that generates an int, and a callback that accepts an int:

Task<int> task = ...;
Action<int> f = ...;

Now I want to set it up so that once the task has completed and returned its integer result, the callfack f will be called with that integer – without requiring the main thread to wait for the task to complete:

  1. As I understand it, the typical solution for this is the Task.ContinueWith method:

    task.ContinueWith(t => f(t.Result));
    
  2. However, one could also get a TaskAwaiter for the task, and use its event-like interface:

    TaskAwaiter<int> awaiter = task.GetAwaiter();
    awaiter.OnCompleted(() => f(awaiter.GetResult()));
    

Now, I realize that TaskAwaiter is not intended for common use in application code, and mainly exists to be used internally by the await keyword.
But in order to deepen my understanding of the TPL, I'm wondering:

Is there any practical difference between solutions (1) and (2)?

For example,

  • ...regarding on which thread the callback will be invoked?
  • ...regarding what happens when an exception is thrown in the task or callback?
  • ...any other side-effects?
like image 681
smls Avatar asked Apr 19 '18 12:04

smls


1 Answers

One difference is that

task.ContinueWith(t => f(t.Result));

will not capture current synchronization context, and in, for example, UI applications - callback will be executed on thread pool thread. While

TaskAwaiter<int> awaiter = task.GetAwaiter();
awaiter.OnCompleted(() => f(awaiter.GetResult()));

will capture synchronization context, and callback will be executed on UI thread.

Of course you can do the same with ContinueWith:

task.ContinueWith(r => f(r.Result), TaskScheduler.FromCurrentSynchronizationContext());

But that's not what you used in question. So ways provided in your question are different at least in that regard.

There is also a difference in exception representation, accessing Task.Result if task has faulted will throw AggregateException (with one or more exceptions as inner exceptions), while accessing awaiter.GetResult() will not wrap thrown exception in AggregateException and will rethrow it as is (and if there were multiple exceptions, like from Task.WhenAll - all except one will be ignored and just one will be thrown).

like image 66
Evk Avatar answered Sep 23 '22 00:09

Evk