Как использовать DbContext с TPL (Задачи)? - PullRequest
0 голосов
/ 23 мая 2019

Я пытаюсь использовать TPL, чтобы уменьшить время выполнения приложения. Приложение использует DbContext, и само задание запрашивает базу данных примерно три раза, используя асинхронные методы (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 и других веб-сайтах. </p> <pre><code>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, но все эти попытки, казалось, усугубили ситуацию. В нынешнем виде я получаю InvalidOperationException несколько раз при каждом запуске программы, и программа прерывается, вероятно, в 70% случаев. 30% времени это закончится успешно.

Как правильно устранить исключения, не выполняя все без TPL?

Ответы [ 2 ]

0 голосов
/ 23 мая 2019

Просто создайте его вручную

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);
    }
}

Затем выполните запросы одновременно, заметив, что он все еще будет выполняться в одном потоке.Вы не хотите запускать его в нескольких потоках, потому что если операции ввода-вывода это будут бесполезные траты потоков, которые ничего не делают - просто ждут ответа.

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);
}
0 голосов
/ 23 мая 2019

Самое простое решение для вашей проблемы, как указано, состоит в том, чтобы создать новый экземпляр контекста БД в вашем цикле и использовать его для каждой задачи, например,

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

foreach (var obj in database.Objects)
    {
        var taskDbContext = testServer.Host.Services.GetService<CustomDbContext>();
        tasks.Add(CustomAsyncMethod(obj.Name, taskDbContext , configuration, httpClient));
    }
...