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

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

  • Ключ: varchar (50)
  • Значение: nvarchar (max)

Я планирую хранить большие строки в столбце Значение (> 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(); до / после каждого чтения БД - не помогло

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

1 Ответ

0 голосов
/ 19 декабря 2018

Итак, после исследования я выяснил, что это просто проблема OutOfMemory, потому что после 8-го параллельного запроса, выделяющего 30 МБ * 2 (так как это символ unicode), объем памяти, выделенный .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();

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

...