Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I avoid an "Unobserved Task" exception?

Tags:

c#

.net

task

I've gotten myself into a mess with tasks, and I'm not sure of a good way to get out of it.

I have data I'm going to asynchronously fetch from two different APIs. The connection is dubious, and I have a tight time constraint on how long I can wait. The desired behavior is "I want to wait up to 2 seconds to get both sets of data, if 2 seconds expires I'll take any that have completed, even if that's nothing."

I pulled this off with a sort of janky setup like this: (It already runs on a worker thread, so the synchronous waits aren't a problem.)

Task<IData> firstTask = _firstDataSource.GetDataAsync()
Task<IData> secondTask = _secondDataSource.GetDataAsync()

var whenBothTasksFinish = Task.WhenAll(firstTask, secondTask);

var timeoutTask = Task.Delay(TimeSpan.FromSeconds(2));

Task.WhenAny(whenBothTasksFinish, timeoutTask).Wait()

bool timedOut = timeoutTask.IsCompleted

if (timedOut) {
    return newIData[0];
}

var sources = new List<IData>();
if (firstTask.IsCompleted && !firstTask.IsFaulted) {
    sources.Add(firstTask.Result);
} else if (firstTask.IsFaulted) {
    LogException(firstTask.Exception);
}

// Same block as above for secondTask

It's messy, but I was cramped for time when I wrote it. Maybe there's a better way to go about it, that's going to be on my mind tonight. Right now I'm confused as to how to fix this one.

The problem happens when network connectivity is turned off, both of my IData tasks fail. Since I log their exceptions, I satisfy the conditions that would otherwise lead to UnobservedTaskException. But their exceptions also bubble into the Task.WhenAll() and, presumably, the Task.WhenAny(). That eventually gets finalized and my app throws UnobservedTaskException. Oddly enough, that doesn't seem to consistently crash the app, but it does sometimes which is why I'm concerned.

I've looked around and it seems like one way I could deal with this is a fairly simple continuation:

whenBothTasksFinish.ContinueWith(t => LogException(t.Exception), TaskContinuationOptions.OnlyOnFaulted);

The only reason I'm not just doing that is I like how the current structure lets me provide a more individualized log message: I care about which task threw the exception. I don't want to log the messy AggregateException I get, but haven't looked too much into unraveling it.

It's late and I haven't spent a lot of time rethinking this algorithm. Is there either a way to make sure I acknowledge the task exceptions or a way to restructure this to get the results I want without involving so many moving parts?

like image 492
OwenP Avatar asked Oct 28 '25 10:10

OwenP


1 Answers

I tried a lot of different things, and arrived at this solution:

  whenBothTasksFinish.ContinueWith((parent) => {
    var flattened = parent.Exception.Flatten();
    flattened.Handle((ex) => true);
  }, TaskContinuationOptions.OnlyOnFaulted);

In normal circumstances, this would be a bad idea. It's the equivalent of an empty try/catch. But in my scenario, I'm going to handle each exception separately later. This makes the framework quit complaining that I haven't handled the exceptions from the WhenAll().

Oddity: when I debug in Xamarin Studio, holding a breakpoint for too long can sometimes cause the WhenAll() task to get collected before that continuation has run. I could probably deal with that via GC.KeepAlive() but meh.

like image 88
OwenP Avatar answered Oct 30 '25 01:10

OwenP