如何将 DbContext 与 TPL(任务)一起使用?
How to use DbContext with TPL (Tasks)?
我正在尝试使用 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
创建,它与我在 Whosebug 和其他网站上看到的不同。
</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 的情况下 运行 正确地消除异常?
如前所述,解决问题的最简单方法是在循环中创建一个新的数据库上下文实例,并将其用于每个任务,例如
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));
}
手动实例化即可
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);
}
我正在尝试使用 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
创建,它与我在 Whosebug 和其他网站上看到的不同。
</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 的情况下 运行 正确地消除异常?
如前所述,解决问题的最简单方法是在循环中创建一个新的数据库上下文实例,并将其用于每个任务,例如
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));
}
手动实例化即可
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);
}