I have written a lot about the conversion of DasBlog Web Forms to ASP.NET Core code, check out some of those posts here:
- Generating a Hang Dump for ASP.NET Core app
- Converting a HttpHandler to Middleware
- Creating TagHelpers with Core MVC
- Hacking in Public with DasBlog
- Razor Pages - An easier way to start web development
- In-Memory Cache with ASP.NET Core
- ASP.NET Core Web–Current Principle
- Creating Themes for ASP.NET Web Core
- AddIISUrlRewrite - From Web Forms to Web Core
However, it was not until I started integrating features that would add or delete a blog posts that I realized how much the threading paradigm had changed. In fact I am realizing now that I took for granted the degree to which ASP.NET (as in System.Web) protected me from myself. Let me explain.
The .NET framework includes the concept of a SynchronizationContext, and so most developers who have been doing multithreaded programming have to some degree taken advantage of an architecture that protects them silently. However, with the introduction of ASP.NET Core, there is no SynchronizationContext. So I think it is now more important than ever to understand what the SynchronizationContext did for us and what programming without it now means.
SynchronizationContext in ASP.NET (System.Web)
Asynchronous pages were introduced to ASP.NET is version 2, before this time every request needed a thread until the request completed, which as you might imagine is highly inefficient. With asynchronous pages, the thread handling the request could begin each operation and then return back to the ASP.NET thread pool. At the conclusion of the operation, another thread from the ASP.NET thread pool would be able to complete the request. SynchronizationContext was designed for ASP.NET to manage this process, here are the most important aspects of its work:
- Provides a way to queue a unit of work to a context
- Every thread has a "current" context
- Keeps a count of outstanding asynchronous operations
For this discussion the most noteworthy thing to consider for ASP.NET (System.Web) SynchronizationContext is that it executes "exclusively", put another way, each delegate executes one at a time (but not necessarily in order).
Threading in ASP.NET Core
Whether you knew about these ideas before or not it is important to repeat that ASP.NET Core has no SynchronizationContext! This means that as you port code from, say, a System.Web ASP.NET MVC app to run on Core, it is possible that everything will compile and run fine, however, as soon as a nominal workload is applied you may see classic signs of race conditions or other types of object corruption. Here are the most important ideas to remember when running without SynchronizationContext:
- Task continuations are queued against the thread pool and can run in parallel
- HttpContext is not thread safe!
- No deadlocks if you block a Task with Task.Wait or Task.Result
We can look more closely at what that means for bullet point one, in the following code sample we gather up a list of fifty Tasks and then perform a WhenAll:
public Task GatherListOfScores() { List<int> listOfScores = new List<int>(); var jobs = new Task[50]; for(int i=0; i<jobs.Length; i++) { jobs[i] = GetScoreAsync(listOfScores, i); } return Task.WhenAll(jobs); } private async Task GetScoreAsync(List<int> scores, int score) { await Task.Delay(200); scores.Add(score); }
The weakness in this code is that we have a reference to List
This is not the case for ASP.NET Core, again there is no queuing, so each task will be scheduled to run on a ThreadPool thread in parallel. This gives each Task the ability to manipulate the List
So how do we fix this? We follow more robust threading practices, the easiest option would be to exchange List
private async Task GetScoreAsync(List<int> scores, int score) { await Task.Delay(200); lock(scores) { scores.Add(score); } }
I must admit this issue took me by surprise in DasBlog it just started breaking inexplicably, the fact is SynchronizationContext is excellent at what it does and you tend not to think about it. However, ASP.NET Core is built for speed and so we are necessarily a little closer to the metal and it is incumbent upon us to know how our applications will react on this new platform. For more information on this there is a great presentation from the ASP.NET Core team at a recent NDC Conference.
Is dasBlog in good enough shape I could start using it? Also, I do quite a bit of identity services work involving OIDC and external providers. If I can scrounge time, I could volunteer to help enable signal sign-on in dasBlog.
github.com/poppastring/dasblog-core
Thanks!
Comments are closed.