EF Core - недопустимая попытка вызвать CheckDataIsReady, когда средство чтения закрыто (OutOfMemory)

У меня есть таблица dbo.Cache на сервере SQL с двумя столбцами:

  • Ключ: varchar (50)
  • Значение: nvarchar (макс.)

Я планирую хранить большие строки в столбце Value (> 30 МБ) и запрашивать их в нескольких потоках.

Итак, проблема в том, что когда я выполняю более 9 запросов параллельно, он начинает генерировать исключение System.InvalidOperationException : Invalid attempt to call CheckDataIsReady when reader is closed:

[Fact]
public void TestMemory()
{
    const string key = "myKey1";
    //re-creating record with Value = large 30mb string
    using (var db = new MyDbContext())
    {
        var existingRecord = db.CachedValues.FirstOrDefault(e => e.Key == key);
        if (existingRecord!=null)
        {
            db.Remove(existingRecord);
            db.SaveChanges();
        }

        var myHugeString = new string('*',30*1024*1024);
        db.CachedValues.Add(new CachedValue() {Key = key, Value = myHugeString});
        db.SaveChanges();
    }

    //Try to load this record in parallel threads, creating new dbContext
    const int threads = 10;
    Parallel.For(1, threads, new ParallelOptions(){MaxDegreeOfParallelism = threads}, (i) =>
    {
        using (var db = new MyDbContext())
        {
            var entity = db.CachedValues.FirstOrDefault(c => c.Key == key);
        }
    });
}

Пытался запускать GC.Collect(); GC.WaitForFullGCComplete(); до / после каждого чтения db - не помогло

Пытался имитировать это поведение на нижнем уровне, напрямую считывая данные через sqlDataReader.ExecuteReader(CommandBehavior.SequentialAccess) - выкидывает OutOfMemoryException

Вы пробовали использовать MultipleActiveResultSets = True в строке подключения?

Dominik 18.12.2018 23:13

Да, я сделал - все та же проблема.

Philipp Munin 19.12.2018 00:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
1 683
1

Ответы 1

Итак, после расследования я понял, что это просто проблема OutOfMemory, потому что после 8-го параллельного запроса, выделяющего 30 МБ * 2 (так как это unicode char) объем памяти, выделенный .Net, фактически превышает 1,2 ГБ в моем приложении, что достаточно для моей рабочей станции. net runtime, чтобы начать подавляться из-за нехватки памяти (https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#ephemeral-generations-and-segments).

Итак, сказав, что я ничего не могу придумать, просто повторив операцию чтения (mem allocate), пока она не добьется успеха с принудительным сбором GC в блоке catch.

Retry.Action(() =>
{
    using (var db = new MyDbContext())
    {
        var entity = db.CachedValues.FirstOrDefault(c => c.Key == key);
    }
}).OnException((OutOfMemoryException exception) =>
{
    GC.Collect();
    GC.WaitForFullGCComplete();
    return true;

})
.OnException((InvalidOperationException exception) =>
{
    if (exception.Message != "Invalid attempt to call CheckDataIsReady when reader is closed.")
    {
        return false;
    }

    GC.Collect();
    GC.WaitForFullGCComplete();
    return true;

})
.Run();

Пожалуйста, дайте мне знать, если вы знаете лучшее решение для этого

Ммм ... черт ... вообще-то не лучший вариант. Может быть, открыть вопрос на github NHibernate и спросить совета. Мне кажется, что OutOfMemoryException следует выбросить из фреймворка NHibernate вместо InvalidOperationException. Но в целом с OOM очень сложно справиться.

Dominik 19.12.2018 22:24

Не могу с этим согласиться, выбрасывая какое-то первое последующее исключение вместо корневого OutOfMemory - это определенно ошибка / недоработка. Не стесняйтесь создавать билет для репозитория Entity Framework Core

Philipp Munin 21.12.2018 00:27

Другие вопросы по теме