C# 11 introduced list patterns. List patterns extend pattern matching to match sequences of elements in a list or an array. The goal of this post is not to explain the syntax but to show how to create a compatible type. If you don't know what list patterns are, I recommend reading the following doc
I've recently added a dark theme to this website. The light or dark theme is selected based on the browser / OS settings. In this post, I'll describe how I've added this support!Theme is selected based on browser settings#Step 1: Extracting colors to variablesThe first step is to extract all colors
Although Visual Studio and the dotnet CLI both offer clean commands, neither one really cleans up bin and obj files generated by the build…Keep Reading →
I recently investigated an issue where the tests hang on the CI. It's easier to debug when issues happen on your machine as you can easily attach a debugger and see what's going on. But in this case, the tests were hanging on the CI and I couldn't attach a debugger. I had to find an alternative way
I asked the following question, about code that uses AsyncLocal as well as async calls. Here is the code again:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
using System;
using System.Threading;
using System.Threading.Tasks;
await new Scenario().Run();
public class Scenario
{
public AsyncLocal<bool> Active = new();
private async Task Start()
{
Active.Value = true;
}
public async Task Run()
{
Console.WriteLine(Active.Value);
await Start();
Console.WriteLine(Active.Value);
}
}
view raw
wierd.cs
hosted with ❤ by GitHub
This code prints False twice, the question is why. I would expect that the AsyncLocal value to remain the same after the call to Start(), since that is obviously the point of AsyncLocal. It turns out that this isn’t the case.
AsyncLocal is good if you are trying to pass a value down to child tasks, but it won’t be applicable to other tasks that are called in the same level. In other words, it works for children, not siblings tasks. This is actually even more surprising in the code above, since we don’t do any awaits in the Start() method.
The question is why? Looking at the documentation, I couldn’t see any reason for that. Digging deeper into the source code, I figured out what is going on.
We can use SharpLab.io to lower the high level C# code to see what is actually going on here, which gives us the following code for the Start() method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
[AsyncStateMachine(typeof(<Start>d__1))]
public Task Start()
{
<Start>d__1 stateMachine = default(<Start>d__1);
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>4__this = this;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
view raw
lower.cs
hosted with ❤ by GitHub
Note that we call to AsyncTaskMethodBuilder.Start() method, which ends up in AsyncMethodBuilderCore.Start(). There we have a bunch of interesting code, in particular, we remember the current thread execution context before we execute user code, here. After the code is done running, we restore it if this is needed, as you can see here.
That looks fine, but why would the execution context change here? It turns out that one of the few places that interact with it is the AsyncValue itself, which ends up in the ExecutionContext.SetLocalValue. The way it works, each time you set an async local, it creates a new layer in the async stack. And when you exit an async call, it will reset the async stack to the place it was before the async call started.
In other words, the local in the name AsyncLocal isn’t a match to ThreadLocal, but is more similar to a local variable, which goes out of scope on function exit.
This isn’t a new thing, and there are workarounds, but it was interesting enough that I decided to dig deep and understand what is actually going on.
If you need to just create a new solution file with all projects in all subfolders in it, this should work for you. Scenario For whatever…Keep Reading →
Take a look at the following code, what do you think it will print?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
using System;
using System.Threading;
using System.Threading.Tasks;
await new Scenario().Run();
public class Scenario
{
public AsyncLocal<bool> Active = new();
private async Task Start()
{
Active.Value = true;
}
public async Task Run()
{
Console.WriteLine(Active.Value);
await Start();
Console.WriteLine(Active.Value);
}
}
view raw
wierd.cs
hosted with ❤ by GitHub
Since it obviously doesn’t print the expected value, why do you think this is the case?