I have the following code in a while loop:
Task worker =
Task.Factory
.StartNew(() =>
{
ProduceAsync(param, tokenSource.Token);
}, tokenSource.Token)
.ContinueWith((t) =>
{
OnException(t);
}, TaskContinuationOptions.OnlyOnFaulted);
ProduceAsync looks like this:
private async Task ProduceAsync( List<string> param, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var task = _requestManager.GetProductsAsync(param);
var products;
try
{
products = await task;
_products.Add(products);
}
catch
{
task.GetAwaiter().GetResult();
}
}
When there's an exception inside the async request I would like the program to fail fast, so on OnException(t) I'm cancelling the tasks using tokenSource. The problem here that ProduceAsync does not propagate the exception and OnException isn't being called. How can I get it to propagate the exception?
Note: I copied the code by hand to my phone so there might be compilation errors...
I'm reasonably sure that instead of
Task.Factory.StartNew(() => {
ProduceAsync(param, tokenSource.Token);
}, tokenSource.Token)
.ContinueWith(...
you should be doing:
Task.Run(async () => {
await ProduceAsync(param, tokenSource.Token);
}, tokenSource.Token)
.ContinueWith(...
basically if you don't wait for your async method to complete then it will run separately and the delegate you pass to Task.Factory.StartNew will return immediately. Also you should use Task.Run as its a bit safer.
Also, you may want to do a rethrow with throw in your catch block after you do whatever you want with the exception.
Related
I read about the differences of Task.Run and Task.Factory.StartNew.
Task.Run(() => {});
should be equivalent to
Task.Factory.StartNew(() => {}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
But in my code I expect a deadlock which is not happening because of Task.Factory.StartNew:
private Task backgroundTask;
private async Task DoSomethingAsync()
{
// this should deadlock
await this.backgroundTask.ConfigureAwait(false);
throw new Exception();
}
private async Task Test()
{
this.backgroundTask = Task.Factory.StartNew(async () =>
{
await this.DoSomethingAsync().ConfigureAwait(false);
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
// just wait here for testing/debugging
await Task.Delay(10000).ConfigureAwait(false);
// if no deadlock, this should throw
await this.backgroundTask.ConfigureAwait(false);
}
But it is not deadlocking. The exception in DoSomethingAsync is thrown but never catched.
Awaiting the Task after the Task.Delay is not throwing either, because it is RanToCompletion.
When using Task.Run it is deadlocking as expected:
private Task backgroundTask;
private async Task DoSomethingAsync()
{
// this is deadlocking
await this.backgroundTask.ConfigureAwait(false);
throw new Exception();
}
private async Task Test()
{
this.backgroundTask= Task.Run(async () =>
{
await this.DoSomethingAsync().ConfigureAwait(false);
});
// just wait here for testing/debugging
await Task.Delay(10000).ConfigureAwait(false);
// never reached because of deadlock
await this.backgroundTask.ConfigureAwait(false);
}
Can anybody explain this behaviour?
The method Task.Factory.StartNew when used with an async delegate returns a nested task: Task<Task>. You are assigning this nested task to a variable of type Task, which is allowed because the class Task<TResult> derives from the class Task. What happens then is that you lose the reference to the inner task, so you have no way of awaiting it. When you await this.backgroundTask you are awaiting the outer Task<Task>, but you can't get the result of the await operation, which is a Task, the inner Task, and await it. The inner task has become a fire-and-forget task.
I want to tack on a task at the end of an original task, but would like to preserve the original result and type. The appended task is just for logging purpose, such as writing to console, etc. For example:
Task.Run(() => DateTime.Now.Hour > 12 ? "Hey!" : throw new Exception())
.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
{
Console.WriteLine("Success");
return t.Result;
}
else
{
Console.WriteLine("Failure");
throw t.Exception;
}
});
The type of the original task is Task<string>. Here I return t.Result if the task doesn't encounter error, and I throw t.Exception in case the task encounters error. Looks like the type remains Task<string> but not sure about the exception side.
Is this the right way to do it? Or is there a better way?
There is no reason to rethrow the exception. The task will throw AggregrateException and you can get the real exceptions with InnerExceptions property to handle them.
For logging, you can separate success and failure using TaskContinuationOptions:
var t = Task.Run(() => DateTime.Now.Hour > 12 ? "Hey!" : throw new Exception());
t.ContinueWith(_ => Console.WriteLine("Success"), TaskContinuationOptions.OnlyOnRanToCompletion);
t.ContinueWith(_ => Console.WriteLine("Faiure"), TaskContinuationOptions.OnlyOnFaulted);
The success will be logged only if task executed successfully until the end. And failure will be logged if there was an unhandled exception.
This separates logging and getting the result. So you can just get the result from the first task.
My preferred solution to this problem is to return the t itself, resulting in a continuation of type Task<Task<string>>, and then Unwrap it to get a Task<string>:
Task<string> task = Task.Run(() =>
{
return DateTime.Now.Hour > 12 ? "Hey!" : throw new Exception();
});
Task<string> continuation = task.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
{
Console.WriteLine($"Success: {t.Result}");
}
else
{
Console.WriteLine("Failure or Cancellation");
}
return t;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default)
.Unwrap();
This approach guarantees that all the errors of the original task will be propagated through the continuation, or, if the task is canceled, that the continuation will be also canceled with the same CancellationToken stored inside it.
In the vast majority of cases the tasks fail with only one error¹, so it's simpler to use async/await instead of the cumbersome ContinueWith method:
async Task<T> OnCompletionLog<T>(Task<T> task)
{
try
{
T result = await task.ConfigureAwait(false);
Console.WriteLine($"Success: {t.Result}");
return result;
}
catch
{
Console.WriteLine("Failure or Cancellation");
throw;
}
}
Task<string> continuation = OnCompletionLog(task);
¹ AFAIK the tasks that can fail with more than one error are the Task.WhenAll, the Parallel.ForEachAsync, and the IDataflowBlock.Completion.
I have tasks created in this way:
public static Task<T> RunAsync<T>(Func<T> function, CancellationToken token) {
if (function == null) throw new ArgumentNullException("");
var tcs = new TaskCompletionSource<T>();
ThreadPool.QueueUserWorkItem(_ => {
try {
T result = function();
tcs.SetResult(result);
}
catch (Exception exc) { tcs.TrySetException(exc); }
}, token);
return tcs.Task;
}
I add task in list after this method.
In System.Timer event i check the execution of tasks to enable/disable buttons like this:
if (this.Tasks.Count > 0) {
Task.WaitAll(this.Tasks.ToArray(), this.cancToken);
this.BeginInvoke(new Action(() => EnableControls()));
this.Task.Clear();
}
Well, if tasks finish correctly all works fine but if tasks generate exception by cancellationToken and status changes in "Fault" the WaitAll remains blocked.. Why? I cannot understand.
I have raise cancellation in my Task function like this:
if (cancToken.IsCancellationRequested)
this.cancToken.ThrowIfCancellationRequested();
Thanks.
I can't seem to figure out why my task is not in a faulted state when an exception is thrown in the following sample (assume engine.Send(...) throws an exception):
var receiverTask = new Task<Task>(async () => {
result = await Task.FromResult(engine.Send(someObject));
});
receiverTask.Start();
receiverTask.Wait();
if I do receiverTask.IsFaulted after the receiverTask.Wait() it returns false instead of true as I would expect.
I have been reading that I need to use something like the following construct:
receiverTask.ContinueWith(t => { /* error handling */ },
TaskContinuationOptions.OnlyOnFaulted);
But I can't seem to figure out how to integrate this into the sample code. Do I call this before the Start()? before the Wait()?
I would like the calling program to somehow be aware that the receiverTask has thrown an exception and failed. Any pointers?
If I correctly understand this context, you could leave out the .Start() and use .Wait() somewhat differently.
(I don't know what your engine.Send(someObject) method is, so I have to assume.)
Let's say your method returns a bool (it doesn't matter, but just to write something down.
If it is synchronous, the returned Task could be:
Task<bool> receiverTask = Task<bool>.Run(async () =>
await Task<bool>.FromResult(engine.Send(someObject)));
or
Task<bool> receiverTask = Task.Factory.StartNew(async delegate
{
bool _result = await Task<bool>.FromResult(engine.Send(someObject));
return _result;
}, TaskScheduler.Default).Unwrap();
If it is asynchronous, the returned Task could be:
Task<bool> receiverTask = Task.Run(async () =>
await engine.Send(someObject));
or
Task<bool> receiverTask = Task.Factory.StartNew(async delegate
{
bool _result = await engine.Send(someObject);
return _result;
}, TaskScheduler.Current).Unwrap<bool>();
(However, all these are blocking if there's no other async/await declaration).
Once your Task is scheduled, some Continuation condition can be defined to evaluate its status.
If the Task is in some faulty status (canceled, aborted etc.) its .IsCompleted property reports true, but the .Status property is not TaskStatus.RanToCompletion.
receiverTask.ContinueWith(t =>
{
//Continue on faulted
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
if (receiverTask.IsFaulted)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnFaulted).Wait(0);
receiverTask.ContinueWith(t =>
{
//Continue on canceled
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
if (receiverTask.IsCanceled)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnCanceled).Wait(0);
receiverTask.ContinueWith(t =>
{
//Standard behaviour
Console.WriteLine(receiverTask.GetAwaiter().IsCompleted);
Console.WriteLine(receiverTask.Status.ToString());
}, TaskContinuationOptions.None).Wait();
//This writes only if no errors have been raised
if (receiverTask.Status == TaskStatus.RanToCompletion)
Console.WriteLine("Completed: {0} Result: {1}", receiverTask.GetAwaiter().IsCompleted, receiverTask.Result);
But you might also use a Try/Catch block:
try
{
receiverTask.Wait();
if (receiverTask.Status == TaskStatus.RanToCompletion)
Console.WriteLine("Completed: {0} Result: {1}", receiverTask.GetAwaiter().IsCompleted, receiverTask.Result);
}
catch (Exception)
{
receiverTask.ContinueWith(t =>
{
//With continuation
if (receiverTask.IsFaulted)
Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}, TaskContinuationOptions.OnlyOnFaulted);
//or without
//if (receiverTask.IsCanceled)
//Console.WriteLine(receiverTask.Exception.InnerExceptions[0].Message);
}
I have the following code
var exceptions = new ConcurrentQueue<Exception>();
Task task = Task.Factory.StartNew(() =>
{
try
{
Parallel.Invoke(
async () => await _aViewModel.LoadData(_someId),
async () => await _bViewModel.LoadData(_someId)
);
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
}
}).ContinueWith((continuation) =>
{
if (exceptions.Count > 0) throw new AggregateException(exceptions);
});
I am using Task.StartNew here because the LoadData method use the Dispatcher.StartAsync method to invoke on the main UI thread internally.
The problem I have is that if I force _aViewModel.LoadData to throw an exception it is not caught in the Catch(Exception) clause (nor if I catch AggregateException). I don't understand why!?
Parallel.Invoke is not async-aware. So your async lambdas are being converted to async void methods, which have extremely awkward error semantics (they are not allowed to leave the async void method; instead, they are captured and re-raised directly on the SynchronizationContext that was active at the time the async void method started - in this case, the thread pool).
I'm not sure why you have the Parallel.Invoke in the first place. Since your method is already async, you could just do something like this:
Task task = Task.Factory.StartNew(async () =>
{
try
{
Task.WaitAll(
_aViewModel.LoadData(_someId),
_bViewModel.LoadData(_someId)
);
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
}
})...
P.S. If you have the time, rethink the structure of this whole part of the code. Dispatcher.StartAsync is a code smell. The UI should be (asynchronously) requesting data; the data retrieval objects should not have to know about the UI.
Parallel.Invoke takes an array of Action delegates. It has no means of knowing that your delegates are actually async methods, and therefore it returns before your tasks have completed.
For an in-depth explanation of this behaviour, watch Lucian Wischik's Channel 9 video on the subject.
Try changing your code to use the Task.WhenAll method instead.
var aTask = _aViewModel.LoadData(_someId);
var bTask = _bViewModel.LoadData(_someId);
await Task.WhenAll(aTask, bTask);