繁体   English   中英

.NET实体框架核心,依赖注入和线程处理的DBContext System.ObjectDisposed异常

[英]DBContext System.ObjectDisposed Exception with .NET Entity Framework Core, Dependency Injection and threading

我不确定我是否正确地解决了这个问题。

背景:我以一个控制器动作GET foo()为例。 这个foo()需要去调用bar(),而bar()可能要花很长时间。 因此, 在bar()完成之前(或无论何时)我需要foo()以“ OK”响应

稍微复杂一点是bar()需要访问DBContext并从DB中获取一些数据。 在当前的实现中,当我尝试通过bar访问数据库时,出现“ DBContext System.ObjectDisposed”异常。 有什么想法为什么以及如何可以解决这个问题? 我真的是线程和任务的新手,所以我可能会完全解决这个错误!

我使用依赖注入在启动时提供数据库上下文

     services.AddEntityFrameworkNpgsql()
        .AddDbContext<MyDBContext>()
        .BuildServiceProvider();

然后,我对foo()进行调用,而foo()依次使用新线程调用bar()(也许我做错了吗?):

    public async Task<string> foo(string msg)
    {

        Thread x = new  Thread(async () =>
        {
            await bar(msg);
        });

        x.IsBackground = true;
        x.Start();

        return "OK.";
    }

因此,bar立即尝试访问DBContext来获取某些实体,并引发异常!

未处理的异常:System.ObjectDisposedException:无法访问已处置的对象。 导致此错误的常见原因是,处理从依赖项注入中解决的上下文,然后稍后尝试在应用程序中的其他位置使用相同的上下文实例。 如果在上下文上调用Dispose()或将上下文包装在using语句中,则可能会发生这种情况。 如果使用依赖项注入,则应让依赖项注入容器负责处理上下文实例。 对象名称:“ MyDBContext”。

如果我从线程中取出bar(),那很好,但是当然,直到bar完成非常长的过程后才返回“ OK”,这是我需要解决的问题。

非常感谢您的指导。

使用运行代码进行编辑,但是它仍在等待Task.Run完成,然后返回“确定”。 (快好了?)

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

在Asp.net中,注入项的生存期取决于框架。 一旦foo()返回,Asp就不知道您创建了一个仍需要它提供给您的DbContext的线程,因此Asp处置了上下文,您遇到了问题。

您可以在线程中自己创建一个新的DbContext,然后可以决定何时处置它。 这不是很好,因为如果数据库配置发生更改,那么您现在有两个地方可能需要更新。 这是一个例子:

var optionsBuilder = new DbContextOptionsBuilder<MyDBContext>();
optionsBuilder.UseNpgsql(ConnectionString);

using(var dataContext = new MyDBContext(optionsBuilder.Options)){
    //Do stuff here
}

第二种选择是,Asp.net核心还具有使用IHostedService创建后台服务并在启动时注册后台服务的能力,并完成了依赖项注入。 您可以创建一个运行后台任务的后台服务,然后foo()可以将该任务添加到服务的队列中。 这是一个例子

AddDbContext<>()使用ServiceLifetime.ScopedMyDBContext注册为服务,这意味着将根据Web请求创建DbContext 请求完成后将其处置。

避免该异常的最简单方法是将IServiceScopeFactory注入到控制器中,然后使用CreateScope()创建一个新范围,并从该范围中请求MyDBContext服务(您不必担心DbContextOptions )。 完成工作后,再处置随后处置DbContext范围。 最好使用Task而不是Thread Task API功能更强大,通常具有更好的性能。 它看起来像这样:

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.";
    }
}

您还应该知道, Asp.Net不是执行后台任务的最佳场所(例如,将应用程序托管在IIS中时,由于应用程序池回收,可以将其关闭)

更新

您的ServerLogger.Info("SendBigFile finished."); 不等待client.PostAsync完成时。 它在Task.Run启动新任务后立即记录。 要在client.PostAsync完成后client.PostAsync日志记录,您需要将ServerLogger.Info("SendBigFile finished."); await GoOffAndSend(someProvider, fileProvider, fileBytes);

...
await GoOffAndSend(someProvider, fileProvider, fileBytes);
ServerLogger.Info("SendBigFile finished.");
...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM