1 year ago

#156569

test-img

jool

How to detect if code is being run in a "branched" async context?

First of all I want to apologize for the potential invalid terminology used in the title. I'm not really sure what to call it or if there even is a right word for it. But stick with me and I'll try to explain.

So I've been fiddling around with AsyncLocal<T> for a while and it works well. It's really great that I am able to await stuff and still access my ambient values using it. However, as it occurs, I am also able to access the same ambient value when I at some point do a Task.Run(() => ...). While I realize this is likely to actually be what is usually desired. In my particular case it is not. I would like code that is explicitly being run in parallel to the original sequence of awaited statements to start on a clean slate from AsyncLocal<T>perspective.

From what I have been finding out this seems to be possible by explicitly fiddling around with the ExecutionContext but since the use of the AsyncLocal<T> is hidden inside a framework I don't want users to have to explicitly do stuff like this every time they do a Task.Run() in order to get the expected behavior.

One solution that I come to think of is if there is any way to detect that the code currently being run has been "branched" off of the previous sequence of (a)sync calls.

i.e.


// In primary flow...

await Foo();

// Still here...

Task.Run(() => 
{
   // No longer in the primary flow. But any AsyncLocal<T> assigned outside still has the same value. 

   // Can I detect that this is not the same flow as outside Task.Run(...)?
});

// Still in primary...

await Foo().ConfigureAwait(false);

// Yep, still here...

Edit:

Upon request from Stephen Cleary I'll elaborate a little on why I am currently in this rabbit hole.

We have a system in which we have a tree of operations that is being executed. Each node is implemented as a class and may of course execute an arbitrary number of child operations. As the operations are being executed they build a parallel tree of OperationTrace nodes. Which is basically a serializable representation of the input/output, timing, validation, logging etc from each exection. Quite handy for debugging purposes, statistics etc.

The execution of the operations are of course async, and multiple trees/branches can be executed in parallel.

We store the currently executed node in each "execution path" as OperationContext.Current, impl. by AsyncLocal<T>, and whenever a child operation is completed it pushes its trace into it's parents list of traces for the child operations.

This works really well, except when someone decides to parallelize the the execution of the child operations. In fact it works rather well here too but the debugging experience is somewhat broken since the order of the child operation executions are no longer predictable.

Typically we want the experience to be like this:

await childOperationX.ExecuteAsync(...);

// Break here and watch 'OperationTrace.Last' to view the trace of X.

Now if the code is something like this:

var t0 = Task.Run(async () => 
{
     await childOperationX.ExecuteAsync(...);
     await childOperationY.ExecuteAsync(...);

});

var t1 = Task.Run(async () => 
{
     await childOperationP.ExecuteAsync(...);
     await childOperationQ.ExecuteAsync(...);


    // Break here and OperationTrace.Last may be any of [Q, X, Y]
});

await Task.WhenAll(t0, t1);

So what we did to solve this was to add:

OperationContext.BranchAsync(async () => ...);

which can be used instead of Task.Run() and that creates an intermediary trace node under which the traces for each child operation is put.

While this works well, it would be really nice to not have to use that, and simply detect in each operation that the "async context" in which this operation is executed is not the same as the one the parent was executed and therefore there needs to be an extra trace node which represents the branch of parallelization.

.net

asynchronous

async-await

thread-local-storage

executioncontext

0 Answers

Your Answer

Accepted video resources