Hangfire与.NET Core、EF并发异常:多Worker并行实现咨询
Hey there! Let's tackle this problem head-on. That error you're seeing is super common when mixing Hangfire's parallel execution with EF Core's non-thread-safe DbContext. Here's how to fix it and get your workers running smoothly in parallel:
First: Understand the Root Cause
The error "A second operation started on this context before a previous operation completed" happens because EF Core's DbContext is not thread-safe. If multiple Hangfire tasks (or workers) try to use the same DbContext instance at the same time, you'll hit this conflict.
Step 1: Ensure Each Task Uses an Independent DbContext
You need to make sure every Hangfire job gets its own, isolated DbContext instance. Here are two reliable ways to do this:
Option 1: Use Dependency Injection with Scoped Services
Hangfire defaults to resolving singleton services, so you need to configure it to support scoped dependencies (like your DbContext).
First, create a custom JobActivator to handle scoped service resolution:
public class ScopedHangfireActivator : JobActivator { private readonly IServiceProvider _serviceProvider; public ScopedHangfireActivator(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public override object ActivateJob(Type jobType) { // Create a new scope for each job to get fresh scoped services using var scope = _serviceProvider.CreateScope(); return scope.ServiceProvider.GetRequiredService(jobType); } }
Then register it when setting up Hangfire in your Program.cs (or Startup.cs):
builder.Services.AddHangfire(config => { config.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")); // Attach our custom activator to handle scoped services config.UseActivator(new ScopedHangfireActivator(builder.Services.BuildServiceProvider())); }); // Configure Hangfire Server with parallel workers builder.Services.AddHangfireServer(options => { // Set number of parallel workers (adjust based on your server's capacity) options.WorkerCount = Environment.ProcessorCount * 2; });
Now, your job class can accept a scoped DbContext via constructor injection:
public class YourJobService { private readonly YourDbContext _dbContext; public YourJobService(YourDbContext dbContext) { _dbContext = dbContext; } public async Task RunScheduledTask() { // Your database logic here - this uses a unique DbContext per job run var data = await _dbContext.YourEntities.ToListAsync(); // ... rest of your task } }
Option 2: Manually Create DbContext in the Job
If you don't want to mess with custom activators, you can directly create a DbContext instance inside your job method:
public async Task RunScheduledTask() { var dbOptions = new DbContextOptionsBuilder<YourDbContext>() .UseSqlServer("YourConnectionString") .Options; // Create a new DbContext for this job execution using var dbContext = new YourDbContext(dbOptions); // Execute your database operations safely await dbContext.YourEntities.AddAsync(new YourEntity()); await dbContext.SaveChangesAsync(); }
Step 2: Configure Hangfire for Parallel Workers
To enable multiple workers running jobs in parallel, just adjust the WorkerCount when starting the Hangfire Server:
builder.Services.AddHangfireServer(options => { // Use 2x the number of CPU cores as workers (tweak based on your workload) options.WorkerCount = Environment.ProcessorCount * 2; // Optional: Define queues if you want to prioritize certain jobs options.Queues = new[] { "critical", "default" }; });
Step 3: Handle Long-Running Jobs (If Needed)
If your task takes longer than 1 hour (your scheduled interval), you might want to prevent overlapping runs. Use MisfireHandlingMode when setting up your recurring job:
RecurringJob.AddOrUpdate( "hourly-task", () => new YourJobService().RunScheduledTask(), Cron.Hourly, misfireHandlingMode: MisfireHandlingMode.Ignore );
This tells Hangfire to skip triggering a new job if the previous one is still running.
Final Tips
- Never use static DbContext instances in Hangfire jobs - they're a guaranteed way to hit concurrency issues.
- Make your job methods
async Taskinstead of synchronous to maximize worker efficiency (especially for IO-bound tasks like database calls). - Monitor your Hangfire dashboard (enabled via
app.UseHangfireDashboard()) to track worker status and job execution.
内容的提问来源于stack exchange,提问作者Nitin

