Also wenn ich das richtig überschaue, erreicht man den selben Effekt auch deutlich einfacher.
Man braucht nur ein AutoResetEvent Objekt und die ThreadPool Funktion RegisterWaitForSingleObject.
Alle Operationen werden dann über das AutoResetEvent synchronisiert. Das erreicht man, indem man die Funktionen dann über RegisterWaitForSingleObject statt mit QueueUserWorkItem im Threadpool registriert.
Das würde schon ausreichen, um den Threadpool während einer Schreiboperation nicht mit wartenden Threads vollzumüllen. Die Queue würde dann vom ThreadPool übernommen. Das würde z.B. so aussehen:
AutoResetEvent waitObject = new AutoResetEvent(true);
// true damit erste Operation sofort ausgeführt wird
void RuftSchreibenAuf()
{
ThreadPool.RegisterWaitForSingleObject( waitObject, WriteCallback, null, Timeout.Infinite, true );
}
void RuftLesenAuf()
{
ThreadPool.RegisterWaitForSingleObject( waitObject, ReadCallback, null, Timeout.Infinite, true );
}
void WriteCallback( object state, bool timedOut )
{
try {
// hier findet das echte Schreiben statt
}
finally {
waitObject.Set(); // gibt für die nächste Operation frei
}
}
void ReadCallback( object state, bool timedOut )
{
waitObject.Set(); // gibt die nächste Operation sofort frei
// Lesen findet dann hier statt
// weil vor dem eigentlichen Lesen schon freigegeben wird,
// könnten auch mehrere Leseoperation parallel stattfinden
}
Damit gibt es noch ein Problem mit der anderen Richtung. Wenn gerade ein paar Threads fleißig am Lesen sind, und einer will was Schreiben, dann wird das nicht verhindert. Er kann einfach anfangen obwohl die Leser noch nicht fertig sind.
Das könnte man zum Beispiel mit dem ReaderWriterLock in den Griff bekommen. Im Ganzen hätte man dann maximal einen Schreibethread im Wartezustand, solange noch Leseoperationen laufen, die vorher aufgerufen wurden. Leseoperationen die nach einer Schreibeoperation aufgerufen werden, würden vom ThreadPool gequeued werden und würden keinen Thread belegen, bis der Schreiber fertig ist.
Im Ganzen würde der Code dann zum Beispiel so aussehen:
AutoResetEvent waitObject = new AutoResetEvent(true);
// true damit erste Operation sofort ausgeführt wird
ReadWriterLock readWriterLock = new ReadWriterLock();
void RuftSchreibenAuf()
{
ThreadPool.RegisterWaitForSingleObject( waitObject, WriteCallback, null, Timeout.Infinite, true );
}
void RuftLesenAuf()
{
ThreadPool.RegisterWaitForSingleObject( waitObject, ReadCallback, null, Timeout.Infinite, true );
}
void WriteCallback( object state, bool timedOut )
{
readWriterLock.AcquireWriterLock( Timeout.Infinite );
try {
// hier findet das echte Schreiben statt
}
finally {
readWriterLock.ReleaseWriterLock();
waitObject.Set(); // gibt für die nächste Operation frei
}
}
void ReadCallback( object state, bool timedOut )
{
readWriterLock.AcquireReaderLock( Timeout.Infinite );
try {
waitObject.Set(); // gibt die nächste Operation sofort frei
// Lesen findet dann hier statt
}
finally {
readWriterLock.ReleaseReaderLock();
}
}
Nicht wirklich sonderlich umständlich finde ich. Und den Aufwand kann man sich sparen, wenn man paralleles Lesen nicht braucht, da würde Variante 1 auch reichen, wenn man das Lesen genauso strickt wie das Schreiben (also Set() erst am Ende aufrufen). Dann würde immer nur eine Operation gleichzeitig laufen, der Rest würde über den ThreadPool gequeued werden. Ich glaube sowieso nicht so richtig, dass paralleles Lesen etwas bringen würde. Selbst mit 4 Prozessoren teilen die sich doch immernoch die gleiche Schnittstelle um auf die Festplatte zuzugreifen.
Das ist zumindest was mir in den ersten 10 Minuten dazu einfällt. Mich würde da mal eure Kritik interessieren. Scheint mir vom Ergebniss her aber ziemlich analog zu funktionieren. Man könnte bestimmt noch was verbessern, und vieleicht ists auch völliger Blödsinn.