You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core应用中DbContext并发问题的排查与解决求助

解决ASP.NET Core后台任务中DbContext并发问题

问题根源

EF Core的DbContext实例非线程安全,你的代码中多个后台任务同时共享同一个DbContext实例,导致并发操作冲突。即使尝试了DbContextFactory,若仍在多任务间传递或共享上下文实例,依然会触发该错误。

正确解决方案

每个后台任务必须使用独立的DbContext实例,具体修改如下:

1. 重构BackgroundJobService依赖注入

移除构造函数中直接注入的BookStoreContext,仅保留IDbContextFactory<BookStoreContext>及其他必要服务:

public class BackgroundJobService : IBackgroundJobService
{
    private readonly ILogger<BackgroundJobService> _logger;
    private readonly IDbContextFactory<BookStoreContext> _contextFactory;
    private readonly IFileHandleService _fileService;
    private readonly IOcrService _ocrService;

    public BackgroundJobService(IDbContextFactory<BookStoreContext> contextFactory, 
                                IFileHandleService fileService, 
                                IOcrService ocrService,
                                ILogger<BackgroundJobService> logger)
    {
        _logger = logger;
        _contextFactory = contextFactory;
        _fileService = fileService;
        _ocrService = ocrService;
    }
}

2. 修改RunOCRJob方法

仅在获取待处理章节列表时创建上下文,且只加载必要字段(避免不必要的关联查询),后续每个任务独立创建上下文处理:

public async Task RunOCRJob()
{
    _logger.LogInformation("Starting OCR job.");

    // 获取待处理的章节ID和路径(仅加载必要数据)
    List<ChapterOCRViewModel> toRunOCR;
    using (var context = _contextFactory.CreateDbContext())
    {
        toRunOCR = await context.BookChapters
            .Where(c => !c.IsOcrRequested && c.Book.ChapterExtracted)
            .OrderBy(o => o.Id)
            .Take(20)
            .Select(c => new ChapterOCRViewModel(c.Id, c.Path))
            .ToListAsync();
    }

    // 为每个章节创建独立的后台任务
    var ocrTasks = toRunOCR.Select(chapter => RunBackgroundJob(chapter)).ToList();
    await Task.WhenAll(ocrTasks);

    _logger.LogInformation("OCR job completed.");
}

3. 修改RunBackgroundJob方法

每个任务内部独立创建DbContext实例,完成OCR后更新数据并独立保存:

public async Task RunBackgroundJob(ChapterOCRViewModel chapter)
{
    try
    {
        // OCR处理(无数据库操作)
        var chapterFilePath = _fileService.GetChapterFilePath(chapter.Path);
        var pageContent = await _ocrService.ReadTextFromPdfAsync(chapterFilePath);

        // 独立创建上下文处理当前章节的数据库更新
        using (var context = _contextFactory.CreateDbContext())
        {
            var chapterEntity = await context.BookChapters
                .Include(cont => cont.Content)
                .FirstOrDefaultAsync(c => c.Id == chapter.Id);

            if (chapterEntity == null)
            {
                _logger.LogWarning($"Chapter ID: {chapter.Id} not found.");
                return;
            }

            if (chapterEntity.Content == null)
                chapterEntity.Content = new ChapterContent();

            chapterEntity.Content.Text = pageContent;
            chapterEntity.OcrAt = DateTime.UtcNow;
            chapterEntity.Status = StatusEnum.ScheduledIndex;
            chapterEntity.IsOcrRequested = true;

            // 当前任务独立保存更改
            await context.SaveChangesAsync();
            _logger.LogInformation($"OCR for Chapter ID: {chapter.Id} completed successfully.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogError($"Error while OCR for Chapter ID: {chapter.Id}. Error message: {ex.Message}");
    }
}

关键改动说明

  • 上下文隔离:每个后台任务在自己的using块中创建DbContext,确保任务间上下文完全独立,避免并发冲突。
  • 数据拆分:获取待处理列表时仅加载必要字段(ID和路径),减少内存占用,且后续任务重新查询实体,避免上下文跟踪问题。
  • 独立保存:每个任务完成后立即保存自己的更改,不需要等待所有任务完成再统一保存,降低锁冲突概率。

额外优化建议

  • 可以为OCR任务添加批量标记:在获取待处理章节时,先将IsOcrRequested设为true并保存(用事务保证原子性),避免重复抓取同一章节。
  • 控制并发任务数量:通过SemaphoreSlim限制同时运行的OCR任务数,避免数据库连接池耗尽。

内容的提问来源于stack exchange,提问作者Rasik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 07:05:07