无法访问 Task.Run 中已处置的对象

Ale*_*jic 2 c# entity-framework dependency-injection asp.net-core

我正在使用.NET Core 3.1。我想运行一些后台处理,而用户不必等待它完成(大约需要 1 分钟)。因此,我Task.Run这样使用:

public class MyController : Controller
{
    private readonly IMyService _myService;

    public MyController(IMyService myService)
    {
        _myService = myService;
    }

    public async Task<IActionResult> Create(...)
    {
        await _myService.CreatePostAsync(...);
        return View();
    }
}

public class MyService : IMyService
{
    private readonly MyDbContext _dbContext;
    private readonly IServiceScopeFactory _scopeFactory;

    public MyService(MyDbContext dbContext, IServiceScopeFactory scopeFactory)
    {
        _dbContext = dbContext;
        _scopeFactory = scopeFactory;
    }

    public async Task CreatePostAsync(Post post)
    {
        ...
        string username = GetUsername();
        DbContextOptions<MyDbContext> dbOptions = GetDbOptions();
        Task.Run(() => SaveFiles(username, dbOptions, _scopeFactory));
    }

    private void SaveFiles(string username, DbContextOptions<MyDbContext> dbOptions, IServiceScopeFactory scopeFactory)
    {
        using (var scope = scopeFactory.CreateScope())
        {
            var otherService = scope.ServiceProvider.GetRequiredService<IOtherService>();
            var cntxt = new MyDbContext(dbOptions, username);

            Post post = new Post("abc", username);

            cntxt.Post.Add(post); <----- EXCEPTION

            cntxt.SaveChanges();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我在标记行中收到以下异常:

System.ObjectDisposedException: 'Cannot access a disposed object. Object name: 'IServiceProvider'.'
Run Code Online (Sandbox Code Playgroud)

为什么会出现这种情况?我对 MyDbContext 使用了自定义构造函数(而不是scope.ServiceProvider.GetRequiredService<MyDbContext>()),因为我需要保存一项附加属性(用户名)以供以后在重写方法中使用。

public partial class MyDbContext
{
    private string _username;
    private readonly DbContextOptions<MyDbContext> _options;

    public DbContextOptions<MyDbContext> DbOptions { get { return _options; } }

    public MyDbContext(DbContextOptions<MyDbContext> options, string username) : base(options)
    {
        _username = username;
        _options = options;
    }

    ... other overriden methods
}
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

Joh*_*lay 9

首先,不要在服务中隐藏线程池操作;让调用代码决定是否在线程池上运行操作:

当您使用依赖注入时,框架会DbContext在 HTTP 请求结束时处理您的内容。

您需要将服务范围工厂注入控制器,并从那里请求服务:

public class MyController : Controller
{
    private readonly IMyService _myService;
    private readonly IServiceScopeFactory _scopeFactory;

    public MyController(IMyService myService, IServiceScopeFactory scopeFactory)
    {
        _myService = myService;
        _scopeFactory = scopeFactory;
    }

    public async Task<IActionResult> Create(...)
    {
        HostingEnvironment.QueueBackgroundWorkItem(SaveInBackground);
        return View();
    }

    private async Task SaveInBackground(CancellationToken ct)
    {
        using (var scope = scopeFactory.CreateScope())
        {
            var scopedService = scope.ServiceProvider.GetRequiredService<IMyService>();
            await scopedService.CreatePostAsync(...);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

HostingEnvironment.QueueBackgroundWorkItem工作方式与 类似Task.Run,只不过它确保应用程序在所有后台工作项完成之前不会关闭。

您的服务需要是这样的:

public class MyService : IMyService
{
    private readonly MyDbContext _dbContext;

    public MyService(MyDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task CreatePostAsync(Post post)
    {
        _dbContext.Post.Add(post);

        await _dbContext.SaveChangesAsync();
    }
}
Run Code Online (Sandbox Code Playgroud)

更新

要将附加参数传递给SaveInBackground

private async Task SaveInBackground(YourParam param)
Run Code Online (Sandbox Code Playgroud)

然后调用如下:

HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => SaveInBackground(yourParam));
Run Code Online (Sandbox Code Playgroud)

  • `HostingEnvironment.QueueBackgroundWorkItem` 在 .NET Core 3 中不可用。我应该使用什么来代替? (6认同)