Take advantage of the new System.Threading.Lock object in C# 13 to more easily implement performant thread-safe applications in .NET Core. Credit: Oksana Shufrych / Shutterstock The C# programming language has provided support for thread synchronization using the lock keyword since its earliest versions. By using a lock statement, you ensure that only one thread can execute the body of the statement at a time. Any other thread is blocked until the lock is released. Now C# has an even better, more elegant way to manage thread synchronization, using the new Lock class introduced in C# 13 and .NET 9. The benefits of the Lock object include reduced memory overhead and faster execution—i.e., using the Lock class consumes fewer CPU cycles than using the lock statement. In this article, we’ll examine how we can use this enhanced API to build thread-safe applications in .NET Core. To work with the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here. Create a console application project in Visual Studio 2022 First off, let’s create a .NET Core 9 console application project in Visual Studio 2022. Assuming you have Visual Studio 2022 installed, follow the steps outlined below to create a new .NET Core 9 console application project. Launch the Visual Studio IDE. Click on “Create new project.” In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed. Click Next. In the “Configure your new project” window, specify the name and location for the new project. Click Next. In the “Additional information” window shown next, choose “.NET 9.0 (Standard Term Support)” as the framework version you would like to use. Click Create. We’ll use this .NET 9 console application project to work with the Lock class in the subsequent sections of this article. Thread synchronization in C# Thread synchronization prevents multiple threads from accessing a shared resource concurrently by synchronizing their access to a block of code (also known as the critical section). A critical section refers to a portion of the source code in your application used to provide access to shared resources. In C#, you should use locks to restrict access to a critical section. The lock keyword in C# provides exclusive access to a shared resource to one and only one thread until the lock is released. All other threads just wait for access to the resource. In essence, the lock statement ensures that, at a given point in time, one and only one thread can access the critical section. Here’s how the lock statement is used in C#: lock (_sharedObj) { //Write your code here } You can also write the same code using Monitor.Enter/Monitor.Exit as shown in the code example given below. private static readonly object _sharedObj = new(); Monitor.Enter(_sharedObj); try { //Write your code here } finally { Monitor.Exit(_sharedObj); } However, whenever you’re using the old style of locking resources in C#, you should use the lock statement, as illustrated in the following code. public class Stock { private readonly object _lockObject = new(); private int _itemsInStock = 100; public void UpdateStock(int numberOfItems, bool flag) { // Acquire the lock lock (_lockObject) { if (flag) _itemsInStock += numberOfItems; else _itemsInStock -= numberOfItems; } } public int ItemsInStock => _itemsInStock; } Introducing the System.Threading.Lock class in C# C# 13 and .NET 9 introduce a new thread synchronization type called System.Threading.Lock, which provides enhanced thread synchronization capabilities. The code below shows how the Lock class is defined in the System.Threading namespace. public sealed class Lock { public Lock(); public bool IsHeldByCurrentThread { get; } public void Enter(); public Scope EnterScope(); public void Exit(); public bool TryEnter(); public ref struct Scope { public void Dispose(); } } The following code snippet illustrates the simplest way to use the new Lock object in C#. var mutex = new new System.Threading.Lock(); lock (mutex) { Console.WriteLine("Inside the critical section."); } Note that the preceding code snippet will compile to the following code. Lock.Scope scope = new Lock().EnterScope(); try { Console.WriteLine("Inside the critical section."); } finally { scope.Dispose(); } Note too that the Lock.EnterScope() method returns a ref struct that contains a Dispose() method to reclaim the memory occupied by the object. Using the new Lock object in C# 13 The code snippet below shows how you can implement a lock on a shared resource using the new Lock object in C# 13. public class Stock { private readonly Lock _lockObject = new(); private int _itemsInStock = 100; public void UpdateStock(int numberOfItems, bool flag) { // Acquire the lock using (_lockObject.EnterScope()) { if (flag) _itemsInStock += numberOfItems; else _itemsInStock -= numberOfItems; } } public int ItemsInStock => _itemsInStock; } When you use the Lock keyword to implement thread synchronization in C#, the Lock.Scope object manages the process of acquiring and releasing the locks. As a result, the locks acquired are safely released even when an exception is thrown at runtime. Benchmarking traditional locks vs. the new Lock keyword Let us now benchmark the performance of both the traditional and new approaches to implementing locks on your shared resources. To do this, we’ll take advantage of the open-source library BenchmarkDotNet. You can learn how to use this library to benchmark applications in .NET Core in a previous article. First we’ll need to install the BenchmarkDotNet NuGet package in our project. Select the project in the Solution Explorer window, then right-click and select “Manage NuGet Packages.” In the NuGet Package Manager window, search for the BenchmarkDotNet package and install it. Alternatively, you can install the package via the NuGet Package Manager console by running the command below. dotnet add package BenchmarkDotNet Next, for our performance comparison, we’ll update the Stock class to include both a traditional lock and the new approach. To do this, replace the Update method you created earlier with two methods, namely, UpdateStockTraditional and UpdateStockNew, as shown in the code example given below. public class Stock { private readonly Lock _lockObjectNewApproach = new(); private readonly object _lockObjectTraditionalApproach = new(); private int _itemsInStockTraditional = 0; private int _itemsInStockNew = 0; public void UpdateStockTraditional(int numberOfItems, bool flag = true) { lock (_lockObjectTraditionalApproach) { if (flag) _itemsInStockTraditional += numberOfItems; else _itemsInStockTraditional -= numberOfItems; } } public void UpdateStockNew(int numberOfItems, bool flag = true) { using (_lockObjectNewApproach.EnterScope()) { if (flag) _itemsInStockNew += numberOfItems; else _itemsInStockNew -= numberOfItems; } } } Now, to benchmark the performance of the two approaches, create a new C# class named NewLockKeywordBenchmark and enter the following code in there. [MemoryDiagnoser] [Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] [RankColumn] public class NewLockKeywordBenchmark { [Params(10, 100, 1000, 10000)] public int N; [Benchmark] public void UpdateStockTraditional() { Stock stock = new Stock(); for(int i = 0; i Executing the benchmarks Finally, let’s execute the benchmarks. The code snippet given below shows how you can run the benchmarks in the Program.cs file. using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; var summary = BenchmarkRunner.Run(typeof(NewLockKeywordBenchmark)); Finally, to compile the application and execute the benchmarks, you should write the following piece of code in the console. dotnet run -p NewLockKeywordDemo.csproj -c Release Figure 1 shows the results of the executed benchmarks. As evident in our simple example, the new Lock object in C# executes consistently faster than the legacy lock keyword. Figure 1. The new Lock object executes faster than the old lock keyword. IDG The new Lock object in C# 13 enables you to reap the benefits of the new API by just changing the type of the object you need to lock. That said, you should know the performance costs associated with locks, whether you’re using the old style or the new Lock object. Additionally, note that using locks can make your code more complicated, and if you don’t handle thread synchronization correctly, your code could even become prone to deadlocks.