I have following code to handle my TaskContinuations
. I am bit confused because I have below OnlyOnFaulted
block which I expect will be entered if the task throws an unhandled exception.
However, unhandled exception, handled exception that is rethrown using throw, or cancellation will land in the OnlyOnCanceled
block.
GetDataAsync(id).ContinueWith((antecedant) =>
{
// do something when async method completed
}, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith((antecedant) =>
{
var error = antecedant.Exception.Flatten(); //so when is this called if everything is cought by OnCancelled below?
}, TaskContinuationOptions.OnlyOnFaulted)
.ContinueWith((antecedant) =>
{
// this is fired if method throws an exception or if CancellationToken cancelled it or if unhandled exception cought
var error = "Task has been cancelled";
}, TaskContinuationOptions.OnlyOnCanceled);
I would expect that re-thrown errors and cancellations will land in the OnlyOnCanceled
block whereas unhandled exception will land in the OnlyOnFaulted
block
Note that I cannot just await GetDataAsync
because this is called in a method called from View's c-tor. I explained that in this post NetworkStream ReadAsync and WriteAsync hang infinitelly when using CancellationTokenSource - Deadlock Caused by Task.Result (or Task.Wait)
UPDATE
Instead using code above, I am using Task.Run like below. I am decorating the lambda passed into Task.Run with async to provide "Async all the way" as recommended by Jon Goldberger at https://blog.xamarin.com/getting-started-with-async-await/
Task.Run(async() =>
{
try
{
IList<MyModel> models = await GetDataAsync(id);
foreach (var model in models)
{
MyModelsObservableCollection.Add(model);
}
} catch (OperationCancelledException oce) {}
} catch (Exception ex) {}
});
This felt a better solution since I can wrap the code inside Task.Run with try...catch block and the exception handling is behaving as I would expect.
I am definitely planning to give a try to suggestion offered by Stephen Cleary at https://msdn.microsoft.com/en-us/magazine/dn605875.aspx as it seam to be a cleaner solution.
As I said in my other answer, you should use await
, and, since this is a constructor for a ViewModel, you should synchronously initialize to a "Loading..." state and asynchronously update that ViewModel to a "Display Data" state.
To answer this question directly, the problem is that ContinueWith
returns a task representing the continuation, not the antecedent. To simplify the code in your question:
GetDataAsync(id)
.ContinueWith(A(), TaskContinuationOptions.OnlyOnRanToCompletion);
.ContinueWith(B(), TaskContinuationOptions.OnlyOnFaulted)
.ContinueWith(C(), TaskContinuationOptions.OnlyOnCanceled);
A()
will be called if GetDataAsync(id)
runs to completion. B()
will be called if A()
faults (note: not if GetDataAsync(id)
faults). C()
will be called if B()
is canceled (note: not if GetDataAsync(id)
is canceled).
There are a couple of other problems with your usage of ContinueWith
: it's missing some flags (e.g., DenyChildAttach
), and it's using the current TaskScheduler
, which can cause surprising behavior. ContinueWith
is an advanced, low-level method; use await
instead.