blgrnboy blgrnboy -4 years ago 155
C# Question

C# TPL Task propagate exceptions - Multi-level task

The

Run()
method (first block of code) makes a call to
GetImpairedNodesFromCASpectrumAsync()
, which in turn makes a call to
GetRoutersOn3GBackupNodeStatusesAsync()
.

Currently, if any of the tasks in
GetRoutersOn3GBackupNodeStatusesAsync()
fail (due to an exception), I get a very generic Exception in the
Run()
method that says the task was cancelled.

How can I ensure that any Tasks in my call stack end up returning the original Exception back to the run method, so that I can handle it there?

public override void Run(ref DevOpsScheduleEntryEventCollection events)
{
// I want to be able to catch any exceptions thrown from tasks in GetRoutersOn3GBackupNodeStatusesAsync()

Task<NetworkDeviceNodeStatus[]> CasImapairedNodesTask =
CasOperations.GetImpairedNodesFromCASpectrumAsync();
Task.WaitAll(CasImapairedNodesTask);
NetworkDeviceNodeStatus[] CasImpairedNodes = CasImapairedNodesTask.Result.ToArray();
}

internal virtual async Task<NetworkDeviceNodeStatus[]> GetImpairedNodesFromCASpectrumAsync()
{
#if DEBUG
Debug.WriteLine("Entering GetNodesInCriticalCondition()");
Stopwatch sw = new Stopwatch();
sw.Start();
#endif

// Execute both tasks. Throw an Exception if any errors.
try {
var nodesWithCircuitsDown = new List<NetworkDeviceNodeStatus>();

Task<NetworkDeviceNodeStatus[]> getAlarmingRoutersStatusesTask = null;
Task<NetworkDeviceNodeStatus[]> getActive3GRoutersStatusesTask = null;

getAlarmingRoutersStatusesTask = GetAlarmingRouterNodeStatusesAsync();
getActive3GRoutersStatusesTask = GetRoutersOn3GBackupNodeStatusesAsync();

await getAlarmingRoutersStatusesTask;
await getActive3GRoutersStatusesTask;

var threeGNodeStatuses = new List<NetworkDeviceNodeStatus>();
var offlineNodeStatuses = new List<NetworkDeviceNodeStatus>();


// Check if any nodes were hard down, but quickly came up on 3G
foreach (var status in getAlarmingRoutersStatusesTask.Result) {
var threeGStatus = getActive3GRoutersStatusesTask.Result.
FirstOrDefault(x => x.DeviceRetrievalId == status.DeviceRetrievalId);

if (threeGStatus == null) {
offlineNodeStatuses.Add(status);
}
}

foreach (var status in getActive3GRoutersStatusesTask.Result) {
threeGNodeStatuses.Add(status);
}

nodesWithCircuitsDown.AddRange(threeGNodeStatuses);
nodesWithCircuitsDown.AddRange(offlineNodeStatuses);

Trace.TraceInformation("{0} nodes with main data circuit down.", nodesWithCircuitsDown.Count);
#if DEBUG
sw.Stop();
Debug.WriteLine("Leaving GetNodesInCriticalCondition(). [" + sw.Elapsed.TotalSeconds + "]");
#endif
return nodesWithCircuitsDown.ToArray();
} catch (AggregateException ae) {
StringBuilder sb = new StringBuilder();
foreach (var e in ae.Flatten().InnerExceptions) {
sb.Append(e.Message + "\n");
}
throw new Exception("One of more errors occured while retrieving impaired nodes.\n" + sb.ToString());
}
}

virtual internal async Task<NetworkDeviceNodeStatus[]> GetRoutersOn3GBackupNodeStatusesAsync()
{
List<Branch3GInfo> branchActive3GInfos = new List<Branch3GInfo>();

var nodeStatuses = new List<NetworkDeviceNodeStatus>();
Task<Branch3GInfo[]> getActive3GRoutersTask = GetNodesOn3GBackupAsyncInternal();
NetworkDeviceNodeStatus[] deviceStatuses = new NetworkDeviceNodeStatus[0];

Task getBasicInfoTasks = await getActive3GRoutersTask.ContinueWith(async x =>
{
branchActive3GInfos = x.Result.Where(y => y.Status == Branch3GInfo.Branch3GStatus.Active).ToList();
Trace.TraceInformation("Found " + x.Result.Count() + " CAS Nodes on 3G backup.");

foreach (var branchActive3GInfo in branchActive3GInfos) {
await branchActive3GInfo.RouterInfo.GetBasicInfoAsync();
Trace.TraceInformation("Retrieved ModelBasicInfo for "
+ branchActive3GInfo.RouterInfo.GetBasicInfo());
}

}, TaskContinuationOptions.OnlyOnRanToCompletion);

await getBasicInfoTasks.ContinueWith(x =>
{
deviceStatuses = GetNetworkDeviceNodeStatuses(branchActive3GInfos.ToArray());
return deviceStatuses;
}, TaskContinuationOptions.OnlyOnRanToCompletion);

return deviceStatuses;
}

Answer Source

Your problem is due to ContinueWith, which will cancel its continuation when the conditions are not met (i.e., TaskContinuationOptions.OnlyOnRanToCompletion).

As a general rule, use await instead of ContinueWith:

virtual internal async Task<NetworkDeviceNodeStatus[]> GetRoutersOn3GBackupNodeStatusesAsync()
{
  List<Branch3GInfo> branchActive3GInfos = new List<Branch3GInfo>();

  var nodeStatuses = new List<NetworkDeviceNodeStatus>();
  NetworkDeviceNodeStatus[] deviceStatuses = new NetworkDeviceNodeStatus[0];
  var result = GetNodesAsync();
  deviceStatuses = GetNetworkDeviceNodeStatuses(branchActive3GInfos.ToArray());
  return deviceStatuses;
}

private async Task<Branch3GInfo[]> GetNodesAsync()
{
  var result = await GetNodesOn3GBackupAsyncInternal();
  branchActive3GInfos = result.Where(y => y.Status == Branch3GInfo.Branch3GStatus.Active).ToList();
  Trace.TraceInformation("Found " + x.Result.Count() + " CAS Nodes on 3G backup.");

  foreach (var branchActive3GInfo in branchActive3GInfos) {
    await branchActive3GInfo.RouterInfo.GetBasicInfoAsync();
    Trace.TraceInformation("Retrieved ModelBasicInfo for "
            + branchActive3GInfo.RouterInfo.GetBasicInfo());
  }
  return result;
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download