DBContext System.ObjectDisposed Исключение с ядром .NET Entity Framework, внедрение зависимостей и многопоточность - PullRequest
0 голосов
/ 08 февраля 2019

Я не уверен, правильно ли я все делаю.

Справочная информация: у меня есть действие контроллера GET foo () в качестве примера.Этот foo () должен пойти и вызвать bar (), а bar () может занять много времени.Итак, Мне нужно, чтобы foo () ответила «ОК» до (или независимо от того, когда) выполнения bar () .

Небольшая сложность заключается в том, что bar () требуется для доступа к DBContext.и получить некоторые данные из БД.В моей текущей реализации я получаю исключение «DBContext System.ObjectDisposed», когда пытаюсь получить доступ к БД через панель.Любые идеи, почему и как я могу обойти это, пожалуйста?Я действительно новичок в многопоточности и задачах, так что я могу полностью понять, что это неправильно!

Я использую внедрение зависимостей, чтобы предоставить контекст БД при запуске

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

Затем я вызываю 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 () из потока, это нормально, но, конечно, "OK" не возвращается, пока bar не завершит очень длинный процесс, что является проблемой, которая мне нужначтобы обойти.

Большое спасибо за руководство, пожалуйста.

РЕДАКТИРОВАТЬ с рабочим кодом, НО он все еще ожидает завершения 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);
    }
}

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

AddDbContext<>() регистрирует MyDBContext как сервис с ServiceLifetime.Scoped, что означает, что ваш DbContext создается для каждого веб-запроса.Он удаляется после завершения запроса.

Самый простой способ избежать этого исключения - внедрить IServiceScopeFactory в контроллер, а затем создать новую область, используя CreateScope(), и запросить службу MyDBContext из этой области (вам не нужно беспокоиться о DbContextOptions).Когда работа будет завершена, тогда утилизируйте ту область, которая впоследствии располагает DbContext.Вместо Thread лучше использовать Task.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, его можно закрыть из-за перезапусков пула приложений)

update

Ваш 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.");
...
0 голосов
/ 08 февраля 2019

В 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 () может добавить задачу в очередь службы. Вот пример .

...