Skip to content

Weird AsyncLocal behaviour in MAUI #4198

@jamescrosswell

Description

@jamescrosswell

Problem

The MauiCommunityToolkitMvvmEventsBinder has some code that wraps AsyncRelayCommand execution in a transaction... however it does something unexpected.

The first time this event triggers, there's no transaction on the scope and so a transaction gets created here.

Later, when the relay command finishes, the associated transaction is finished, which should reset the transaction on the scope (setting this to null):

scope.ResetTransaction(this);

And immediately after span.Finish() is called, this is the case (scope.Transaction has been reset).

However when the code runs for subsequent AsyncRelayCommands, for some reason the (completed) transaction is back on the scope again!

We created a workaround in that PR but we really should dig into this to understand what's going on.

Initial analysis

Originally posted by @jamescrosswell in #4125 (comment)

I don't quite understand what's happening yet, but most stuff on the Scope is global in MAUI cause we always set IsGlobalMode = true for MAUI apps. Scope.Transaction is an exception:

private readonly AsyncLocal<ITransactionTracer?> _transaction = new();

AsyncLocal<T> has a constructor overload that lets you wire up a ValueChanged event handler, which I did for Scope._transaction and there is definitely something very odd going on.

Putting a breakpoint in there, the first time it triggers is when our OnPropertyChanged fires, assigning Scope.Transaction for the first time (with the new transaction):

image

The it fires twice more with args.ThreadContextChanged == true:
image
image

Then it fires again from our OnPropertyChanged event handler when the command finishes running (this is finishing the transaction and resetting Scope.Transaction = null:
image

However immediately after that there's a thread context change again and the property gets restored to a non-null value!!! 🐛 🐞 😞

image

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .net code

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions