-1

I'm at a loss. I have a Task factory that is starting a function:

        Task.Factory.StartNew(() => ServerGetSecureLog(ServerAndProjectorArchive);
        private static void ServerGetSecureLog(string archivePath)
        {
            var localClientManager = InitializeConnection();
            var destinationPath = $"{ServerSecureLogFile}";

            var result = ServerGetBasicSecureLogAsync(localClientManager);
            var sw = StartStopwatch();
            //Wait upto 120s
            if (!result.Wait(TimeSpan.FromSeconds(120))) // <<< Exception thrown here.
            {
                StopStopwatch(sw);
                log.Warn($"{MethodBase.GetCurrentMethod().Name} took too long to execute (timeout exceeded).");
            }
            else
            {
                StopStopwatch(sw);

Thing is, I'm getting an System.AggregateException being thrown as shown above. I must be going insane., because to debug this, I put breakpoints on EVERY line of code before that, on that same line the exception is being thrown and even the line that starts the thread and the lambda that is called, yet NONE of them are getting hit. Going up one stack frame and I get to the call of the lambda that I put the breakpoint on.

Variable state: local variable state localClientManager looks to be uninitialized, so seems that the IP got hijacked somehow.

I wouldn't expect the optimizer would do anything on a DEBUG build and there are no unsafe areas in the code. What could cause such bazar behaviour?

Edit

Could Remote Debugging be an issue? Unfortunately, I can't put a debugger on that system, and can't really run the app from a local machine.

Adrian
  • 10,246
  • 4
  • 44
  • 110
  • Perhaps using `Task.Run` would help? See: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory.startnew?view=net-6.0#remarks – Ibrennan208 Jun 29 '22 at 22:45
  • Other than that, what about the code that comes after `Task.Factory.StartNew(() => ServerGetSecureLog(ServerAndProjectorArchive);` ? And I don't mean in your `ServerGetSecureLog` method, but rather the actual lines of code that follow. – Ibrennan208 Jun 29 '22 at 22:46
  • @Ibrennan208, spawns other tasks and eventually waits. – Adrian Jun 29 '22 at 22:53
  • What do you mean by that? An aggregate exception means that your code is probably failing in multiple places. It's possible you have code failing while your task is running in the background. – Ibrennan208 Jun 29 '22 at 22:55
  • This happens some times, and yes remote debugging can affect it. If you want to know what's actually happening put some logging in using an atomic logger - something that always logs in the order of received events. – Corey Jun 29 '22 at 22:57
  • @Ibrennan208 Aggregate exception is whenever a task throws an exception. The aggregate in this case is the single exception that was thrown. You have to look at the inner exception to see what happened. – Corey Jun 29 '22 at 22:58
  • @Corey They aren't exclusive to `Task`, but yes "*It is used extensively in the Task Parallel Library (TPL) and Parallel LINQ (PLINQ).*" - https://learn.microsoft.com/en-us/dotnet/api/system.aggregateexception?view=net-6.0#remarks ; My suggestions to include surrounding code were because there were no mentions of the system configuration being questioned. – Ibrennan208 Jun 29 '22 at 23:04
  • I do see now in the image, the exception count is 1, so yes it should just be as simple as checking out the inner exception to figure out the root of the cause. – Ibrennan208 Jun 29 '22 at 23:09
  • I looked at the inner exception and it stated: `The communication object, System.ServiceModel.Channels.ClientWebSocketTransportDuplexSessionChannel, cannot be used for communication because it has been Aborted.`. Looking at the stack trace where it was rethown, it shows that it's comming from an external API. I'll have to dig into it further later. – Adrian Jun 30 '22 at 00:55

1 Answers1

0

Whenever a Task run to completion in failed state - that is, when method that run that task somehow failed - it throws AggregateException.

This is due to the fact that a Task can be continued:

var t = SomeAsyncMethod().ContinueWith(a=> { });

If SomeAsyncMethod throws an exception and ContinueWith also throws an exception, the aggregated exception would have all exceptions for that call.

However, when you're using async/await calls and does try/catch, c# async state unrolls aggregated exceptions and throws original exception on your code.

The problem in your code is related with the fact that GetServerLog method is synchronous and is calling an Task method. You can:

var localClientManager = InitializeConnection();
var destinationPath = $"{ServerSecureLogFile}";
//Wait upto 120s
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120));
// assumes that this method will look at the token to stop processing and bubbling up a OperationCancelledException/TimeoutException if token expires.
// localClientManager can be null if InitializeConnection returns null or depends on other lazy/async initialization steps.

var result = ServerGetBasicSecureLogAsync(localClientManager, cts.Token);

var sw = Stopwatch.StartNew();
try 
{
    // This can cause deadlocks it Thread pool threads are waiting for a free thread and other locations are doing Waits: https://stackoverflow.com/questions/13140523/await-vs-task-wait-deadlock

    //result.WaitOne(TimeSpan.FromSeconds(120));
    result.GetAwaiter().GetResult();

}
catch(MySimpleException)
{
}
catch(OperationCancelledException)
{
}
finally
{
   sw.Stop();
    // could enclose cts in using statement
   cts.Dispose();
}

Because GetAwaiter().GetResult() is the same method called by async state, it also unrolls the aggregated exception and by using try/catch, you can look at operation cancelled as well.

Also, you don't need to create a Task to run an async method:

async Task SomeAsyncMethod()
{
    var localClientManager = InitializeConnection();
    var destinationPath = $"{ServerSecureLogFile}";
    //Wait upto 120s
    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120));
    // assumes that this method will look at the token to stop processing and bubbling up a OperationCancelledException/TimeoutException if token expires.
    // localClientManager can be null if InitializeConnection returns null or depends on other lazy/async initialization steps.
    var sw = Stopwatch.StartNew();
    try 
    {
        await ServerGetBasicSecureLogAsync(localClientManager, cts.Token);
    }
    catch(MySimpleException)
    {
    }
    catch(OperationCancelledException)
    {
    }
    finally
    {
        sw.Stop();
        // could enclose cts in using statement
       cts.Dispose();
    }
}

// this call will never throw as long as method is catching and handling exceptions
SomeAsyncMethod().GetAwaiter().GetResult();

  • Hmmm. There was no call to `ContinueWith()`. Thanks for the info on not needing to create a task. I'm new to C# and I've inherited this code. Looks like maybe the originator may not have known exactly what they were doing or perhaps this functionality wasn't available when it was being produced. What's the difference between using `result.Wait()` and `new CancellationTokenSource()`? – Adrian Jun 30 '22 at 01:03
  • `GetServerLog`? Do you mean `ServerGetSecureLog`? – Adrian Jun 30 '22 at 01:09