Using a Lock in a Web API Action Method

Download full source code.

This is one of those posts that there may not be any “good” practical application for, but it might be helpful in some edge cases.

And I learned something interesting about web browser behavior, more on that later.

The idea

What if you wanted to acquire an exclusive lock on a resource while executing a Web API action method?

Generally speaking, this is not a good idea. You don’t want to block threads in an API style application. However, I have worked with companies that have done some very unusual things with APIs, such as using an endpoint to initiate a long-running process that should not be run concurrently.

There were better ways to do this, but that’s what they were doing, and they weren’t going to change it.

The code

The code is very straightforward. You create a lock object and use it in a lock statement in the action method.

Here is the basic idea -

 1// snip..
 2object myLock = new();
 3
 4app.MapGet("/", () =>
 5{
 6    // do stuff before lock
 7    lock (myLock)
 8    {
 9        // simulate long running process
10        Thread.Sleep(5000);
11    }
12    // do stuff after lock
13    return "Done!";
14});

That’s it. You have a lock around the code in the action method.

Here is a complete example with logging to see what is going on -

 1var builder = WebApplication.CreateBuilder(args);
 2var app = builder.Build();
 3
 4Lock myLock = new(); // lock object, if you are using earlier versions of C# use the below line instead
 5//object myLock = new();
 6
 7app.MapGet("/", () =>
 8{
 9    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
10    string requestId = Guid.NewGuid().ToString().Substring(0, 4);
11    Console.WriteLine($"{requestId} {DateTime.Now.ToString("mm:ss.fff")} Processing request...trying to lock");
12
13    lock (myLock)
14    {
15        Console.WriteLine($"{requestId} {DateTime.Now.ToString("mm:ss:fff")} Lock acquired");
16        Thread.Sleep(5000); // can't await inside lock
17        Console.WriteLine($"{requestId} {DateTime.Now.ToString("mm:ss:fff")} Delay complete, returning response");
18    }
19
20    Console.WriteLine($"{requestId} {DateTime.Now.ToString("mm:ss:fff")} Lock released\n");
21    return $" {requestId} {DateTime.Now.ToString("mm:ss:fff")}. Hello World!";
22});
23app.Run();

Testing it out

You need to send multiple requests quickly to see the locking in action.

Don’t use a browser. I tried with both FireFox and Brave. They would not send multiple requests concurrently to the same URL even from two tabs. I assume this is some kind of optimization in the browser.

Instead, use a tool like Postman, Fiddler or the built in HTTP client in your IDE.

Here is my .http file for VS Code -

@LockingInAMethod_HostAddress = https://localhost:5000

GET {{LockingInAMethod_HostAddress}}/
Accept: application/json

Send the request multiple times quickly and you will see the locking in action in the console output.

The output will shows three requests being processed, each gets a thread of their own, and a request id. The first request acquires the lock and starts processing. The other two requests wait for the lock to be released before they can proceed.

05b7 ThreadId 12
05b7 55:35.597 Processing request...trying to lock
05b7 55:35:600 Lock acquired
63a1 ThreadId 9
63a1 55:35.863 Processing request...trying to lock
1b04 ThreadId 13
1b04 55:36.169 Processing request...trying to lock
05b7 55:40:600 Delay complete, returning response
05b7 55:40:600 Lock released

63a1 55:40:600 Lock acquired
63a1 55:45:601 Delay complete, returning response
63a1 55:45:601 Lock released

1b04 55:45:601 Lock acquired
1b04 55:50:601 Delay complete, returning response
1b04 55:50:601 Lock released

Conclusion

Again, not something I would recommend, but if you find yourself stuck, this is one way to do it.

Download full source code.

comments powered by Disqus

Related