1

I have the following example code:

public virtual async Task<TResponse> ExecuteAsync<TResponse>(Func<TServiceClient, Task<TResponse>> func)
{
    var st1 = new System.Diagnostics.StackTrace().ToString();
    try
    {
        return await func(_clientInstance);
    }
    catch (Exception ex)
    {
        ThrowServiceException(ex, st1);
    }
}

private void ThrowServiceException(Exception ex, string st1)
{
    var st2 = new System.Diagnostics.StackTrace().ToString();
    if (st1 != st2)    {    }
// ...
    
}

And when I a exception is thrown, the difference between stacks before and after is similar to following. Before:

   at Common.Autorest.AutorestClientWrapper`1.ExecuteAsync[TResponse](Func`2 func)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at Common.Autorest.AutorestClientWrapper`1.ExecuteAsync[TResponse](Func`2 func)
   at Common.Autorest.AutorestClientWrapper`1.Execute[TResponse](Func`2 func)
   at BankingServiceClient.BankApi.GetBufferBalance(CancellationToken cancellationToken)
   at Engine.Services.BalanceWorker.<>c__DisplayClass18_0.<RefreshTreasuryBalance>g__ExecuteTreasuryBalanceRefresh|0(CancellationToken token)
   at Engine.Services.BalanceWorker.ExecuteBalanceRefresh(Action`1 executeRefresh, String loggingName)
   at Engine.Services.BalanceWorker.RefreshTreasuryBalance(Currency currency)
   at Engine.Services.BalanceWorker.<>c__DisplayClass14_2.<.ctor>b__4()
   at Engine.Threading.Runnable.Engine.Threading.IRunnable.RunStep()
   at Engine.Threading.Runner.ExecuteContinously()
   at System.Threading.Thread.StartHelper.Callback(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Thread.StartCallback()

After:

   at Common.Autorest.AutorestClientWrapper`1.ThrowServiceException(Exception ex, String st1)
   at Common.Autorest.AutorestClientWrapper`1.ExecuteAsync[TResponse](Func`2 func)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext()
   at System.Runtime.CompilerServices.TaskAwaiter.<>c.<OutputWaitEtwEvents>b__12_0(Action innerContinuation, Task innerTask)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.ContinuationWrapper.Invoke()
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(Action action, Boolean allowInlining)
   at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
   at System.Threading.Tasks.Task.FinishContinuations()
   at System.Threading.Tasks.Task.FinishStageThree()
   at System.Threading.Tasks.Task.FinishStageTwo()
   at System.Threading.Tasks.Task.FinishSlow(Boolean userDelegateExecute)
   at System.Threading.Tasks.Task.TrySetException(Object exceptionObject)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.SetException(Exception exception, Task`1& taskField)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.SetException(Exception exception)
   at Bank.ApiClient.BufferAccountBankAPI.GetAccountBalanceAsync(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext()
   at System.Runtime.CompilerServices.TaskAwaiter.<>c.<OutputWaitEtwEvents>b__12_0(Action innerContinuation, Task innerTask)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.ContinuationWrapper.Invoke()
   at System.Threading.Tasks.AwaitTaskContinuation.System.Threading.IThreadPoolWorkItem.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()

So, as you can see, stack trace is lost and it is quite hard to figure it out from where the exception is thrown.

I somehow understand why stack so different and I am wondering if is it possible, for debugging purposes, to force all async methods execute immediately to keep stack trace as it is before calling? Maybe with some global variable or something.

The solution described in Is it possible to get a good stack trace with .NET async methods? does not resolve what I want because that one just sanitized "useless" stack items, but in my case stack trace before call is totally different than after call and it seems there is no connection between before and after.

Edit: Given method is called in multiple occasions.

Some looks like this:

public async Task<MarketLimits> GetMarketLimits(CancellationToken cancellationToken)
{
  var response = await _serviceClientWrapper.ExecuteAsync(c => c.GetLimitsAsync(cancellationToken));
  // ...
}

Some like this:

public virtual TResponse Execute<TResponse>(Func<TServiceClient, Task<TResponse>> func)
{
    return ExecuteAsync(client => func(client)).GetAwaiter().GetResult();
}
Rok
  • 451
  • 3
  • 21
  • Looks like something is going wrong at `BankingServiceClient.BankApi.GetBufferBalance` – Joost00719 Jan 13 '23 at 10:16
  • https://stackoverflow.com/a/51218350/7081176 Maybe this helps too – Joost00719 Jan 13 '23 at 10:19
  • I think my issue is quite different. I do not have problem with "readability" of stacktrace, but mine is totally lost, not connected with initial call. And I do not think there is something wrong with the code but I believe this is default async behaviour. – Rok Jan 13 '23 at 11:02
  • I have seen a video a long time ago somewhere on youtube where someone explained that your stacktrace could be all messed up if you do not await your calls. Perhaps you forgot to await somewhere up the callstack? You could try to check that. (Maybe write an async unit test to see if the stacktrace is still lost) – Joost00719 Jan 13 '23 at 11:26
  • The code, where this happen, is awaited... i think this is even more obvious in highly parallel environments which mine is. – Rok Jan 13 '23 at 11:50
  • Can you show the `delegate` you're passing into `ExecuteAsync`. – WBuck Jan 13 '23 at 12:05
  • I updated main question with examples. – Rok Jan 13 '23 at 12:15
  • Does this answer your question? [Is it possible to get a good stack trace with .NET async methods?](https://stackoverflow.com/questions/15410661/is-it-possible-to-get-a-good-stack-trace-with-net-async-methods) – NineBerry Jan 13 '23 at 12:22
  • I do not think so. In this solution they only "sanitize" stack, so they remove useless lines, but my stack does not contains lines which I want. – Rok Jan 13 '23 at 12:37
  • Does `ex.StackTrace` show something different? – Gabriel Luci Jan 13 '23 at 13:43
  • This is the async tax, lovely when the code works but lose a day of your life when it doesn't. Exceptions are caught and recorded, to be thrown later when the details get a lot more murky. It is important to catch the exception at the exact moment it is thrown so you can debug the state of the program. Use Debug > Windows > Exception Settings and tick the box for CLR Exceptions. – Hans Passant Jan 13 '23 at 14:55
  • 1
    @Rok: The [highest-voted answer](https://stackoverflow.com/a/15412558/263693) for the linked question has your answer. – Stephen Cleary Jan 13 '23 at 14:56

0 Answers0