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

EF6多线程调用DataReader出现异常,求解决方案

Fixing Multi-Threading Exceptions with EF6, Repository Pattern & DataReader Multi-Result Sets

Hey there, let's tackle those three exceptions you're running into—they all stem from the same core issue: EF6's DbContext, along with the underlying ADO.NET Connection, Command, and DataReader objects, are not thread-safe. When multiple threads share these objects, you get race conditions that cause those confusing errors. Let's break down each problem and fix them step by step.

Why Each Exception Happens

  1. Invalid attempt to call FieldCount when reader is closed
    This happens when one thread closes the DataReader (or the underlying connection) while another thread is still trying to read from it. Since you're sharing a DbContext/connection across threads, the first thread to finish might dispose of resources the second thread still needs.

  2. There is already an open DataReader associated with this Command which must be closed first
    By default, ADO.NET doesn't allow multiple active DataReaders on a single Command/connection (unless you enable MARS, but that's not a real fix here). If two threads try to execute ExecuteReader on the same shared Command, the second one will fail because the first Reader is still open.

  3. ExecuteReader requires an open and available Connection. The connection's current state is open
    This contradictory error is a race condition: one thread might be in the process of closing the connection while another tries to use it. Even though the connection says it's "open", it's in an unstable state from concurrent access.

The Core Fix: Use Isolated DbContext/Repository Instances Per Thread

EF6's DbContext is designed for short, single-threaded lifetimes. The solution is simple: never share a DbContext or repository instance across threads. Each thread should create its own instance, use it, and dispose of it when done.

Example of a Thread-Safe Repository Implementation

Here's how to adjust your repository to be safe for multi-threaded use:

public class ProductRepository : IDisposable
{
    private readonly AppDbContext _dbContext;

    // Create a fresh DbContext for each repository instance
    public ProductRepository()
    {
        _dbContext = new AppDbContext();
    }

    public (List<Product>, List<Category>) GetProductsAndCategories()
    {
        var products = new List<Product>();
        var categories = new List<Category>();

        using (var command = _dbContext.Database.Connection.CreateCommand())
        {
            command.CommandText = "GetProductsAndCategories"; // Your stored procedure/SQL
            command.CommandType = CommandType.StoredProcedure;

            // Ensure the connection is open (but let using handle closing)
            if (command.Connection.State != ConnectionState.Open)
                command.Connection.Open();

            // Wrap the reader in using to guarantee it's closed properly
            using (var reader = command.ExecuteReader())
            {
                // Read first result set (Products)
                while (reader.Read())
                {
                    products.Add(new Product
                    {
                        Id = reader.GetInt32(reader.GetOrdinal("Id")),
                        Name = reader.GetString(reader.GetOrdinal("Name"))
                    });
                }

                // Move to second result set (Categories)
                reader.NextResult();

                while (reader.Read())
                {
                    categories.Add(new Category
                    {
                        Id = reader.GetInt32(reader.GetOrdinal("Id")),
                        Name = reader.GetString(reader.GetOrdinal("Name"))
                    });
                }
            }
        }

        return (products, categories);
    }

    // Dispose the DbContext when the repository is done
    public void Dispose()
    {
        _dbContext?.Dispose();
    }
}

How to Call It Safely in Multi-Threaded Code

When spinning up threads or using parallel processing, create a new repository instance for each thread:

// Example with Parallel.For
Parallel.For(0, 5, index =>
{
    // Each thread gets its own repository (and thus its own DbContext)
    using (var repo = new ProductRepository())
    {
        var (products, categories) = repo.GetProductsAndCategories();
        // Process your data here—no shared state, no race conditions
        Console.WriteLine($"Thread {index} loaded {products.Count} products");
    }
});

Additional Best Practices

  • Avoid Static DbContexts/Repositories: Static instances are shared across all threads—this is the #1 cause of these exceptions.
  • Use using Statements Everywhere: Wrap DbContexts, Commands, and DataReaders in using blocks to ensure resources are disposed immediately after use, preventing leaks or dangling connections.
  • Skip MARS as a "Fix": While enabling Multiple Active Result Sets in your connection string might resolve the second exception, it won't fix the underlying thread-safety issues with DbContext. You'll still hit the other two errors eventually.
  • Dependency Injection Note: If you're using DI (like in ASP.NET), configure your DbContext with a scoped lifetime (not singleton). This ensures each request/thread gets its own instance.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:18:03