Reputation: 377
I have an infinite loop in a task. Under certain circumstances, this task throws an exception and terminates. Consider the following code snippet.
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
int x = await FirstTask();
window.Title = "FirstTask completed with " + x.ToString();
}
catch (ArgumentException ex)
{
textbox.Text = ex.Message;
}
}
public async Task<int> FirstTask()
{
Task<int> secondTask;
int result;
secondTask = SecondTask();
textbox.Text = "Awaiting SecondTask result";
result = await secondTask;
textbox.Text = result;
secondTask.ContinueWith(async (Task t) =>
{
var thirdTask = ThirdTask();
thirdTask.ContinueWith(
async (m) =>
await Task.Run(() =>
{
throw thirdTask.Exception.InnerException;
}),
TaskContinuationOptions.OnlyOnFaulted);
}, TaskContinuationOptions.OnlyOnRanToCompletion);
return 5;
}
public async Task<int> SecondTask()
{
await Task.Delay(1500);
return 8;
}
public async Task ThirdTask()
{
while (true)
{
await Task.Delay(500);
throw new ArgumentException("thirdException");
}
}
My problems lies in the inability to propagate the exception thrown from ThirdTask to the Button_Click event. Obviously, awaiting it is not an options, since it is an ongoing infinite operation (this is only simplified to fail quickly). I have, however, no problem with awaiting the "short" task which re-throws the exception, if it is only triggered once the ThirdTask fails. Note that I'm not interested in the doings of the ThirdTask unless it fails, that is while I'm able to await the FirstTask in the event handler.
Experimenting showed that even the most simple example doesn't propagate the exception from the ContinueWith block.
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
Task task = Task.Run(async () => { await Task.Delay(1000); });
task.ContinueWith( (t) => { throw new ArgumentException("test"); }, TaskContinuationOptions.OnlyOnRanToCompletion);
}
catch (ArgumentException ex)
{
textbox.Text = ex.Message;
}
}
So, how do I propagate an exception from ContinueWith to the calling context, given that the task that throws it has an infinite loop, which prevents me from awaiting it?
The problem I'm trying to solve is two-fold: First, I need to initialize a resource (FirstTask), in order to do that, I first need to fetch it (SecondTask) and then to begin a process with it (ThirdTask), finally, the initialization of the resource (FirstTask) returns a value indicating the state of the resource, which doesn't depend on the process (ThirdTask). The process (ThirdTask) repeatedly invokes another task (in this case Task.Delay) and performs some work on it, but it can fail. In that case, it throws an exception which needs to be handled.
The second part is the general case of the second code example, of how to throw an exception from ContinueWith to be handled by the calling context.
Upvotes: 0
Views: 541
Reputation: 203811
given that the task that throws it has an infinite loop, which prevents me from awaiting it?
That in no way prevents you from awaiting it. The [easiest] way to handle the case that it throws an exception is specifically to await
it.
You can simply implement the method as such:
public async Task FirstTask()
{
Task<int> secondTask = SecondTask();
textbox.Text = "Awaiting SecondTask result";
textbox.Text = await secondTask;
await ThirdTask();
}
If the click handler needs to both update a texbox with the results of the second operation and update the UI if the third fails, then you need to not wrap both of those operations in FirstTask
and call them directly from the click handler:
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
textbox.Text = "Awaiting SecondTask result";
int x = await SecondTask();
window.Title = "SecondTask completed with " + x.ToString();
await ThirdTask();
}
catch (ArgumentException ex)
{
textbox.Text = ex.Message;
}
}
Upvotes: 1