Entity Framework Thread Safety
More than one thread operating on a single Entity Framework context is not thread safe.
A separate instance of context for each thread is thread-safe. As long as each thread of execution has its own instance of EF context you will be fine.
In your example, you may call that code from any number of threads concurrently and each will be happily working with its own context.
However, I would suggest implementing a 'using' block for this as follows:
// this method is called from several threads concurrently
public void IncrementProperty()
{
using (var context = new MyEntities())
{
context.SomeObject.SomeIntProperty++;
context.SaveChanges();
}
}
Entity Framework DbContext and thread safety
Simple way is, to have one DbContext per request, ASP.NET MVC does all thread safety, each controller instance in ASP.NET MVC is isolated for every request, you don't have to worry about race conditions. As long as you don't create threads and just simply do data transformation in action method using single DbContext, you will not have any problem.
Basically DbContext does nothing, it just queues SQL query to target database, it is the database which handles multi threading, race conditions. To protect your data, you should use transactions and add validations in your database to make sure they are saved correctly
public abstract class DbContextController : Controller{
public AppDbContext DB { get; private set;}
public DbContextController(){
DB = new AppDbContext();
}
protected override void OnDisposing(bool disposing){
DB.Dispose();
}
}
If you inherit any class from DbContextController
and use DB throughout the life of controller, you will not have any problem.
public ActionResult ProcessProducts(){
foreach(var p in DB.Products){
p.Processed = true;
foreach(var order in p.Orders){
order.Processed = true;
}
}
DB.SaveChanges();
}
However, if you use any threads like in following example,
public ActionResult ProcessProducts(){
Parallel.ForEach(DB.Products, p=>{
p.Processed = true;
// this fails, as p.Orders query is fired
// from same DbContext in multiple threads
foreach(var order in p.Orders){
order.Processed = true;
}
});
DB.SaveChanges();
}
Is DbContext thread safe?
It's not thread safe. Simply create a new instance of DbContext
in you thread.
Thread safety with Entity Framework
This type of error occurs when you relate entities created in different instances of DbContext
.
If repositories of UserService
and CodeService
have different contexts this error will occur at some point.
As you says in comments if you are using InstancePerLifetimeScope
probably contexts of each service are different because the repositories are created in different scopes.
You should use InstancePerRequest
to ensure that the context is the same throughout the execution of the entire request.
UnitOfWork and DbContext: thread safety with DI
The source of the problem is that MyContext
is held captive as a Captive Dependency in the following object graph:
MqttClientHostedService
-> PositionService
-> UnitOfWork
-> MyContext
All types in this graph are registered as Transient
, but still, services that act as hosted service (e.g. your MqttClientHostedService
) are resolved only once for the duration of the application and cached indefinately. This effectively makes them a singleton.
In other words, MyContext
is accidentally kept alive by the single MqttClientHostedService
and because multiple messages can come in in parallel, you have yourself a race condition.
The solution is to let each ApplicationMessageReceived
event run in its own unique little bubble (a scope) and resolve a new IPositionService
from within that bubble. For instance:
public class MqttClientHostedService : IHostedService, IDisposable
{
[...]
public MqttClientHostedService(
ILogger<MqttClientHostedService> logger,
IOptions<MqttClientConfiguration> mqttConfiguration,
IServiceProvider provider)
{
this.logger = logger;
this.config = mqttConfiguration;
this.provider = provider;
}
[...]
private async Task MqttClient_ApplicationMessageReceived(
object sender, MqttApplicationMessageReceivedEventArgs e)
{
using (var scope = provider.CreateScope())
{
positionService = scope.ServiceProvider
.GetRequiredService<IPositionService>();
string message = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
await positionService.HandleMessage(message);
}
}
[...]
}
Is a DbContext per thread in Parallel.ForEach safe?
Your pattern is thread-safe. However, at least for SQL Server, if your concurrency is too high, you'll find that your total throughput drops off as contention for database resources increases.
In theory, Parallel.ForEach optimizes the number of threads, but in practice, I have found it allows too much concurrency in my applications.
You can control concurrency with the ParallelOptions optional parameter. Test your use case and see if the default concurrency works well for you.
Your comment: Keeping in mind that, right now anyway, that we are talking about 100's of ids in that code above where most id's represent work that does not end up in any changes to the database and are short lived while a hand full can take minutes to complete and ultimately add 10's of new records to the DB. What MaxDegreesOfParallelism value would you recommend off the top of your head?
Probably 2-3 based on your general description, but it depends on how database intensive ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords
is (vs. performing CPU-bound activities or waiting for IO from files, web service calls, etc). With more than that, if that method is mostly performing DB tasks, you're likely to get locking contention or overwhelm your IO subsystem (disks). Test in your environment to be sure.
It may be worth exploring why ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords
is taking so long to complete for a given Id.
UPDATE
Here's some test code to demonstrate that the threads do not block each other and indeed run concurrently. I removed the DbContext portion for simplicity and since it doesn't affect the threading issue.
class SomeTaskProcessor
{
static Random rng = new Random();
public int Id { get; private set; }
public SomeTaskProcessor(int id) { Id = id; }
public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords()
{
Console.WriteLine($"Starting ID {Id}");
System.Threading.Thread.Sleep(rng.Next(1000));
Console.WriteLine($"Completing ID {Id}");
}
}
class Program
{
static void Main(string[] args)
{
int[] ids = Enumerable.Range(1, 100).ToArray();
Parallel.ForEach(ids, id => {
var processor = new SomeTaskProcessor(id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
});
}
}
Related Topics
Htmlagilitypack -- Does <Form> Close Itself for Some Reason
How to Embed an Application Manifest into an Application Using VS2008
Why Enums Require an Explicit Cast to Int Type
Convert Linq Query Result to Dictionary
How to Fix Error: "Could Not Find Schema Information for the Attribute/Element" by Creating Schema
Auto Create Database in Entity Framework Core
Reading Excel Files as a Server Process
C# Create/Modify/Read .Xlsx Files
Why Does C# Limit the Set of Types That Can Be Declared as Const
Practical Uses for the "Internal" Keyword in C#
How Does Startcoroutine/Yield Return Pattern Really Work in Unity
Why Cannot Ienumerable<Struct> Be Cast as Ienumerable<Object>
Casting List<T> - Covariance/Contravariance Problem
Passing Command-Line Arguments in C#
Convert from Binary Data to an Image Control in ASP.NET
How to Conditionally Compile My C# for Mono VS. Microsoft .Net