Ho una tabella dbo.Cache in SQL server, con due colonne:
Sto pianificando di memorizzare stringhe di grandi dimensioni nella colonna Valore (> 30mb) e interrogarle in più thread.
Quindi il problema è che quando faccio più di 9 query in parallelo, inizia a lanciare un'eccezione 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();
cercato di eseguire GC.Collect(); GC.WaitForFullGCComplete();
prima / dopo ogni db letto - non ha aiutato
Ho cercato di imitare questo comportamento sul livello inferiore leggendo direttamente i dati attraverso sqlDataReader.ExecuteReader(CommandBehavior.SequentialAccess)
- genera OutOfMemoryException
Quindi, dopo un'indagine, ho scoperto che si tratta solo di un problema OutOfMemory perché dopo l'ottava richiesta parallela che assegna 30mb * 2 (poiché è unicode char) la quantità di memoria allocata da .Net in realtà supera la mia app di 1,2 GB, che è sufficiente per la mia workstation. runtime di rete per avviare il chocking sulla mancanza di memoria ( https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#ephemeral-generations-and-segments ).
Detto questo, non c'è molto altro che riesca a fare solo riprovando l'operazione di lettura (mem allocate) finché non riesce a forzare GC a raccogliere nel blocco 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();
Per favore fatemi sapere se conoscete qualche soluzione migliore per questo