Fire-and-forget and incomplete asynchronous stack traces #104289
Replies: 1 comment 1 reply
-
Just about this. I know it's just a side question, but it might nevertheless be addressed. If you don't care, feel free to ignore my entire comment here, as it only deals with this side question. The real answer to this question is: Nobody. It's the stack trace pretending the In reality, the code in the You could imagine your Foo method being converted into something that functions kinda like this (simplified and very likely totally inaccurate): int _state = 0;
void MoveNext()
{
_state++;
switch (_state)
{
case 1:
Console.WriteLine("Before yielding");
PrintStackTrace();
some Task.Yield() magic scheduling a call to MoveNext on a thread-pool thread
return;
case 2:
Console.WriteLine("\nYielded");
PrintStackTrace();
some Task.Delay() magic that schedules a call to MoveNext on a thread-pool thread when Task.Delay() completes
return;
case 3:
Console.WriteLine("\nDelay(0)");
PrintStackTrace();
return;
default:
Oh dang! What are we doing here?
}
} The generated stack trace hides these details of the inner workings of async state machines from you because otherwise the stack trace wouldn't really correlate with the source code anymore. If you want to see what's really going on, print out information of the individual stack frames in the call stack. Like i did here: SharpLab Note how the call stack of the first synchronous part of the
When you look at the call stacks of the two continuations (one after awaiting Task.Yield and the other after Task.Delay):
you'll notice that there is actually no call into |
Beta Was this translation helpful? Give feedback.
-
Suppose I have the following code (SharpLab)
Function
Bar
calls async functionFoo
withoutawaiting
it to complete (fire-and-forget). Now, If you check the output, you will see that, after first async yield, the stack traces printed fromFoo
invoked bybar
(fire-and-forget) will miss the caller (the function that triggersFoo
) information.Console Output
While this is okay for spinning up long-running background tasks, it can be difficult if there are multiple callers invoking
Foo
with fire-and-forget pattern -- if there is an Exception getting thrown fromFoo
, I cannot tell which caller has started theFoo
invocation that triggered the Exception.While I can guess the reason we do not have caller information for fired-and-forgotten
Task
might lies in the performance consideration (there is performance penalty if we were to capture stack traces every time aTask
gets spun up), is my hunch correct?And do you have any suggestion if I want to track "who called
Foo
" in this case? Do we have to do this manually by ourselves?For now I can only think of passing additional parameters to
Foo
, telling it some information about its caller.AsyncLocal
might also work, but I suppose it's basically equivalent to passing an additional parameter down toFoo
.Beta Was this translation helpful? Give feedback.
All reactions