Не удается получить доступ к удаленному объекту в Task.Run - PullRequest
0 голосов
/ 09 апреля 2020

Я использую . NET Core 3.1 . Я хочу запустить некоторую фоновую обработку без необходимости ждать, пока пользователь завершит работу sh (это займет около 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();
        }
    }
}

В отмеченной строке я получаю следующее исключение:

System.ObjectDisposedException: 'Cannot access a disposed object. Object name: 'IServiceProvider'.'

Почему это происходит? Я использовал собственный конструктор (а не scope.ServiceProvider.GetRequiredService<MyDbContext>()) для 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
}

Что я делаю неправильно?

Ответы [ 2 ]

0 голосов
/ 09 апреля 2020

Прежде всего, не скрывайте операцию пула потоков в своей службе; пусть вызывающий код решит, запускать операцию в пуле потоков или нет:

Поскольку вы используете внедрение зависимостей, платформа избавляется от вашего 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(...);
        }
    }
}

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

ОБНОВЛЕНИЕ

Для передачи дополнительных параметров в SaveInBackground:

private async Task SaveInBackground(YourParam param)

Затем вызовите как:

HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => SaveInBackground(yourParam));
0 голосов
/ 09 апреля 2020

Вы должны создать Сервис с жизненным циклом Singleton, внедрить DBContext внутри и поставить в очередь все задачи внутри

...