如何在TPL中使用DbContext(任务)?

时间:2019-05-23 14:00:26

标签: c# entity-framework asp.net-core task-parallel-library

我正在尝试使用TPL减少应用程序的运行时间。应用程序使用DbContext,并且Task本身使用异步方法(FirstOrDefaultAsync)查询数据库大约三次。我开始收到异常,例如:

"System.InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations."

这使我意识到DbContext不是线程安全的,因此我需要一个解决方案。

我尝试为每个上下文创建数据库的新实例,但是我认为我做的不正确。我将在下面显示一些代码来演示我的DbContext的创建,这与我在StackOverflow和其他网站上其他地方看到的不同。

public static async Task Main()
{
    var host = new WebHostBuilder()
               .UseEnvironment("Test")
                   .ConfigureAppConfiguration((builderContext, config) =>
                   {
                       var env = builderContext.HostingEnvironment;
                       config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
                   }).UseStartup<Startup>();

    var testServer = new TestServer(host);

    var database = testServer.Host.Services.GetService<CustomDbContext>();
    database.Database.EnsureCreated();

    var httpClient = testServer.CreateClient();
    httpClient.DefaultRequestHeaders.Add(ApiConstants.General.APIKeyHeaderParm, tenant.APISecurityGuid.ToString()); // this works

    var builder = new ConfigurationBuilder()
               .SetBasePath(Directory.GetCurrentDirectory())
                   .AddJsonFile("appsettings.json");

    var configuration = builder.Build();

    var tasks = new List<Task>();
    foreach (var obj in database.Objects)
    {
        tasks.Add(CustomAsyncMethod(obj.Name, database, configuration, httpClient));
    }
    try
    {
        Task.WaitAll(tasks.ToArray());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
        Console.ReadKey();
    }

    //Some other normal code
}

public static async Task CustomAsyncMethod(string name, CustomDbContext database, IConfigurationRoot configuration, HttpClient httpClient)
{
    //Lines of normal code stuff
    //Inside of nested for loops here
    var first = await database.History.FirstOrDefaultAsync(x => x.Id == id);
    var second = await database.OtherTable.FirstOrDefaultAsync(y => y.Name == first.Name);
    // End nested for loops
    // Lines of other normal code stuff
}

public static async Task Main() { var host = new WebHostBuilder() .UseEnvironment("Test") .ConfigureAppConfiguration((builderContext, config) => { var env = builderContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); }).UseStartup<Startup>(); var testServer = new TestServer(host); var database = testServer.Host.Services.GetService<CustomDbContext>(); database.Database.EnsureCreated(); var httpClient = testServer.CreateClient(); httpClient.DefaultRequestHeaders.Add(ApiConstants.General.APIKeyHeaderParm, tenant.APISecurityGuid.ToString()); // this works var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); var configuration = builder.Build(); var tasks = new List<Task>(); foreach (var obj in database.Objects) { tasks.Add(CustomAsyncMethod(obj.Name, database, configuration, httpClient)); } try { Task.WaitAll(tasks.ToArray()); } catch (Exception ex) { Console.WriteLine(ex.ToString()); Console.ReadKey(); } //Some other normal code } public static async Task CustomAsyncMethod(string name, CustomDbContext database, IConfigurationRoot configuration, HttpClient httpClient) { //Lines of normal code stuff //Inside of nested for loops here var first = await database.History.FirstOrDefaultAsync(x => x.Id == id); var second = await database.OtherTable.FirstOrDefaultAsync(y => y.Name == first.Name); // End nested for loops // Lines of other normal code stuff } 在fooreach循环()中,我尝试创建多个foreach (var obj in database.Objects),多个WebHostBuilders和多个TestServers用于httpClients,但是所有这些尝试似乎使情况变得更糟。按照目前的情况,每次运行该程序时,都会多次抛出CustomAsyncMethod,并且该程序可能会在70%的时间内崩溃。 30%的时间它将成功完成。

如何在没有运行TPL的情况下正确消除异常?

2 个答案:

答案 0 :(得分:0)

针对问题的最简单解决方法是在循环中创建数据库上下文的新实例,并将其用于每个任务,例如


with tf.Session() as sess:
    train_x = np.ones([data_size, num_features])
    train_y = np.ones([data_size])

    # Initialise dataset with data:
    sess.run(iterator.initializer, feed_dict={input_data: train_x, labels: train_y})

    start = time.time()

    while True:
        try:
            o = sess.run(batch)

        except tf.errors.OutOfRangeError as e:
            break
    print('elapsed time for tf.data: ', time.time() - start)


答案 1 :(得分:0)

只需手动实例化

public class Data
{
    public async Task<History> GetHistoryById(int id)
    {
        using (var context = CreateDbContext())
        {
            return await context.History.FirstOrDefaultAsync(h => h.Id == id);
        }
    }

    public async Task<History> GetOtherByName(string name)
    {
        using (var context = CreateDbContext())
        {
            return await context.OtherTable.FirstOrDefaultAsync(o => o.Name == name);
        }            
    }

    public async Task<IEnumerable<MyObject>> GetObjects()
    {
        using (var context = CreateDbContext())
        {
            return await context.Objects.ToListAsync();
        }            
    }

    private CustomDbContext CreateDbContext()
    {
         var options = new DbContextOptionsBuilder<CustomDbContext>()
             .UseSqlServer(_connectionString)
             .Options;

         return new CustomDbContext(options);
    }
}

然后同时执行查询,请注意它仍将在一个线程上运行。您不希望在多个线程上运行它,因为如果进行IO操作,那将浪费线程,而无所事事-只是等待响应。

public static async Task CustomAsyncMethod(int id, Data data)
{
    //  ...

    var first = await data.GetHistoryById(id);
    var second = await data.GetOtherByName(first.Name);

    // ...
}

主要

public static async Task Main()
{
    // Configurations ....

    var data = new Data();
    var objects = await data.GetObjects();

    var tasks = objects.Select(o => CustomAsyncMethod(o.Id, data)).ToArray();

    await Tasks.WhenAll(tasks);
}