Que*_*ner 5 c# multithreading entity-framework dependency-injection entity-framework-core
我不确定我是否正确地处理这个问题。
背景:我有一个控制器操作 GET foo() 作为示例。这个 foo() 需要去调用 bar(),而 bar() 可能需要很长时间。因此,我需要 foo() 在 bar() 完成之前(或无论何时)以“OK”响应。
稍微复杂的是 bar() 需要访问 DBContext 并从 DB 中获取一些数据。在我当前的实现中,当我尝试通过 bar 访问数据库时,我得到了一个“DBContext System.ObjectDisposed”异常。任何想法为什么以及如何解决这个问题?我对线程和任务真的很陌生,所以我可能完全犯了这个错误!
我使用依赖注入在启动时提供数据库上下文
services.AddEntityFrameworkNpgsql()
.AddDbContext<MyDBContext>()
.BuildServiceProvider();
Run Code Online (Sandbox Code Playgroud)
然后我调用 foo() ,它又使用一个新线程调用 bar() (也许我做错了?):
public async Task<string> foo(string msg)
{
Thread x = new Thread(async () =>
{
await bar(msg);
});
x.IsBackground = true;
x.Start();
return "OK.";
}
Run Code Online (Sandbox Code Playgroud)
所以 bar 立即尝试访问 DBContext 以获取一些实体并抛出异常!
未处理的异常:System.ObjectDisposedException:无法访问已处理的对象。此错误的一个常见原因是处理从依赖注入解析的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况。如果你使用依赖注入,你应该让依赖注入容器来处理上下文实例。对象名称:'MyDBContext'。
如果我从线程中取出 bar() ,那很好,但当然“OK”不会返回,直到 bar 完成其非常长的过程,这是我需要解决的问题。
非常感谢您的指导。
使用正在运行的代码进行编辑,但它仍在等待 Task.Run 在返回“OK”之前完成。(差不多好了?)
public async Task<string> SendBigFile(byte[] fileBytes)
{
ServerLogger.Info("SendBigFile called.");
var task = Task.Run(async () =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var someProvider = scope.ServiceProvider.GetService<ISomeProvider>();
var fileProvider = scope.ServiceProvider.GetService<IFileProvider>();
await GoOffAndSend(someProvider, fileProvider, fileBytes);
}
});
ServerLogger.Info("Hello World, this should print and not wait for Task.Run."); //Unfortunately this is waiting even though GoOffAndSend takes at least 1 minute.
return "Ok"; //This is not returned until Task.Run finishes unfortunately... how do I "skip" to this immediately?
}
private async Task GoOffAndSend(ISomeProvider s, IFileProvider f, byte[] bytes)
{
// Some really long task that can take up to 1 minute that involves finding the file, doing weird stuff to it and then
using (var client = new HttpClient())
{
var response = await client.PostAsync("http://abcdef/somewhere", someContent);
}
}
Run Code Online (Sandbox Code Playgroud)
AddDbContext<>()将其注册MyDBContext为服务,ServiceLifetime.Scoped这意味着您DbContext是根据 Web 请求创建的。它在请求完成时被处理。
避免该异常的最简单方法是注入IServiceScopeFactory您的控制器,然后使用该范围创建一个新范围CreateScope()并MyDBContext从该范围请求服务(您无需担心DbContextOptions)。当工作完成时,然后处置随后处置的范围DbContext。而不是Thread更好地使用Task. TaskAPI 更强大,通常具有更好的性能。它看起来像这样:
public class ValuesController : ControllerBase
{
IServiceScopeFactory _serviceScopeFactory
public ValuesController(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public async Task<string> foo(string msg)
{
var task = Task.Run(async () =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<MyDBContext>();
await bar(db, msg);
}
});
// you may wait or not when task completes
return "OK.";
}
}
Run Code Online (Sandbox Code Playgroud)
此外,您应该知道这Asp.Net不是后台任务的最佳位置(例如,当应用程序托管在 IIS 中时,由于应用程序池回收,它可能会被关闭)
完成后您ServerLogger.Info("SendBigFile finished.");无需等待client.PostAsync。它在Task.Run启动新任务后立即记录。要在client.PostAsync完成后记录它,您需要ServerLogger.Info("SendBigFile finished.");在await GoOffAndSend(someProvider, fileProvider, fileBytes);:
...
await GoOffAndSend(someProvider, fileProvider, fileBytes);
ServerLogger.Info("SendBigFile finished.");
...
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2079 次 |
| 最近记录: |