Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

Threadsafe Liste, bei der ein Tasks den exklusiven Zugriff erhalten kann
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1411
Herkunft: NRW

Themenstarter:

Threadsafe Liste, bei der ein Tasks den exklusiven Zugriff erhalten kann

beantworten | zitieren | melden

'n Abend,

ich brauche eine Liste, die von x-beliebig vielen Threads genutzt werden kann. Soweit so gut, mein Problem ist, dass ein Task (oder mehrere) auch exklusiven Zugriff bekommen können soll. Der jeweilige Task, der den exklusiven Zugriff hat, darf beliebig lesen und schreiben, während alle Threads warten müssen, bis der Task den allgemeinen Zugriff wieder frei gibt.

Oder anderes Formuliert:
Viele Threads lesen und schreiben fleißig in einer Liste, dabei wird nie für einen längeren Zeitraum gelockt.
Parallel dazu kann ein Task (also theoretisch mehrere Threads) ebenfalls beliebig lesen oder schreiben, bis er besagten exklusiven Zugriff beantragt. Er wartet dann so lange, bis alle laufenden Aktionen oder ein anderer exklusiver Zugriff beendet sind und bekommt dann z.B. ein Handle-Objekt, mit dem er weiter arbeiten kann.

Gibt es für solche Vorhaben schon vorhandene Klassen in .NET, die das entweder können, oder vereinfachen?
Oder gibt es allgemeine Konzepte, an denen ich mich bei der Umsetzung einer eigenen Lösung orientieren kann?
Oder habe ich etwas total einfaches übersehen? :D

Beste Grüße
private Nachricht | Beiträge des Benutzers
Spook
myCSharp.de - Member



Dabei seit:
Beiträge: 232
Herkunft: Esslingen a.N.

beantworten | zitieren | melden

Hallo Palladin,

für mich klingt das nach ReaderWriterLock Class.
Mit dieser konnen mehrere Threads parallel aus der Liste lesen, aber immer nur ein Thread die Liste verändern.

Grüße
spooky
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1411
Herkunft: NRW

Themenstarter:

beantworten | zitieren | melden

Die Klasse scheint zu tun, was ich brauche, allerdings mit einem ganz entscheidenden Nachteil:
Wechselt der Thread, hab ich einen Deadlock...

Ich arbeite mit Tasks und ein simples "await Task.Delay(1000)" kann dafür sorgen, dass der darauf folgende Code in einem anderen Thread läuft und dann auf immer und ewig darauf wartet, etwas tun zu dürfen.
private Nachricht | Beiträge des Benutzers
FZelle
myCSharp.de - Experte



Dabei seit:
Beiträge: 10042

beantworten | zitieren | melden

Was ist mit der SynchronizedCollection
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1411
Herkunft: NRW

Themenstarter:

beantworten | zitieren | melden

Die synchronisiert ja nur die Zugriffe zwischen den Threads, ich kann aber nicht alle Threads aussperren und nur den Zugriff von einem Tasks aus erlauben.

Diese Liste bietet zwar das SyncRoot-Objekt nach draußen an, allerdings kann ich das nur begrenzt nutzen.

Im Grunde suche ich sowas:

// Thread 1 bis X:
myList.Add(123);
// oder:
lock(list.SyncRoot)
{
    var item = myList.Count + 1;

    myList.Add(count);
}

// Task:
lock (list.SyncRoot)
{
    for (var i = 0; i < 10; i++)
    {
       await Task.Yield(); // <-- Nur zum simulieren verschiedener Threads

       var item = myList.Count + i;

       myList.Add(count);
   }
}

Das funktioniert aber nicht, ein await kann ich (aus gutem Grund) nicht in einem lock nutzen.

Ich suche also sozusagen eine Art Schlüssel, der sämtliche Zugriffe sperrt, außer wenn man bei diesem Zugriff den Schlüssel mit gibt.

Meine Ideal-Vorstellung wäre sowas wie:

// Task:
using (var list2 = list.TakeLock())
{
    for (var i = 0; i < 10; i++)
    {
       await Task.Yield(); // <-- Nur zum simulieren verschiedener Threads

       var item = list2 .Count + i;

       list2 .Add(count);
   }
}

TakeLock() gibt einen Wrapper zurück, der den Zugriff regelt und beim Aufruf von Dispose() die Sperre wieder frei gibt.
private Nachricht | Beiträge des Benutzers
Spook
myCSharp.de - Member



Dabei seit:
Beiträge: 232
Herkunft: Esslingen a.N.

beantworten | zitieren | melden

Also geht es dir primär nicht um das Synchronisieren eines konkreten Zugriffs auf eine Instanz von List<T> (oder ähnlich) sondern du möchtest eher zwei getrennte Zugriffsebenen?

Du könntest mehrere ReaderWriterLocks oder Sync-Objekte verwenden. Die "normalen" Tasks locken immer nur Ebene 1. Wenn ein Task exklusiven Zugriff möchte lockt er ebenfalls Ebene 1 und die Threads dieses Tasks locken nur Ebene 2 um sich intern zu synchronisieren.
private Nachricht | Beiträge des Benutzers
Papst
myCSharp.de - Experte



Dabei seit:
Beiträge: 344
Herkunft: Kassel

beantworten | zitieren | melden

Lässt sich das nicht gut it einer SemaphoreSlim lösen?

-> Initialisiere die Semaphore mit einem Counter, der deiner Anzahl threads enspricht.
-> Jeder Thread der schreiben will holt sich eine Semaphore und gibt sie nach jeder Operation wieder frei
-> Will ein Thread exklusiv Zugriff, holt er sich einfach alle
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1411
Herkunft: NRW

Themenstarter:

beantworten | zitieren | melden

Das Problem bei den genannten Verfahren (ReaderWriterLock, Monitor bzw. lock, Semaphore/Slim) ist, dass die alle nur so lange funktionieren, wie der Thread noch der Selbe ist, wie zum Zeitpunkt, als der Zugriff beantragt wurde.
Bei Tasks muss ich aber damit rechnen, dass ich mehrere Threads habe und die fröhlich hin und her wechseln, ohne dass ich das voraus ahnen kann.

Ich denke daher nicht, dass es ohne z.B. einer Art Lock-Handle-Objekt funktioniert, also dass der Zugriff nur erlaubt wird, wenn eben dieses Handle-Objekt mit gegeben wird. Man sperrt diesen Key und jeder andere Zugriff mit dem selben Key muss warten, bis der Key wieder frei wird.
private Nachricht | Beiträge des Benutzers
T-Virus
myCSharp.de - Member



Dabei seit:
Beiträge: 1768
Herkunft: Nordhausen, Nörten-Hardenberg

beantworten | zitieren | melden

@Palladin007
Warum hast du den mit den Tasks und den ConcurrentCollections deine Probleme?
Ein Task, der z.B. mit Task.Run gestartet wird, hat seinen eigenen Thread weshalb dies kein Problem sein kann.
Nur wenn du z.B. über Task.Factory.StartNew arbeitest, kann es sein, dass dieser erst im aktuellen und einige Zeit später in einem eigenen Thread läuft.

Mir wäre es aber neu, dass die ConcurrentCollections sich nicht mit den Tasks vertragen.

Nachtrag:
Versuchs mal mit der ConcurrentBag<T>

Doku

T-Virus
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von T-Virus am .
Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1411
Herkunft: NRW

Themenstarter:

beantworten | zitieren | melden

Führe folgenden Code ein paar Mal aus:

static async Task Test()
{
    Console.WriteLine(Environment.CurrentManagedThreadId);
    await Task.Run(() => Thread.Sleep(10));
    Console.WriteLine(Environment.CurrentManagedThreadId);
    await Task.Factory.StartNew(() => Thread.Sleep(10));
    Console.WriteLine(Environment.CurrentManagedThreadId);
    await Task.Delay(10);
    Console.WriteLine(Environment.CurrentManagedThreadId);
    await Task.Yield();
    Console.WriteLine(Environment.CurrentManagedThreadId);
}

Die Ausgabe sieht immer anders aus, z.B.:
Zitat
14557
14555
14446
14444
14466
14556
14564

Das Problem dabei ist, dass ich nie weiß, was bei einem await passiert. Wie arbeitet z.B. ein EFCore bei einem ToListAsync? Wie arbeitet jedes x-beliebige Framework, das asynchrone Zugriffe erlaubt, habe ich danach noch den alten Thread?

Wenn ich asynchrone Zugriffe erlauben möchte, muss ich damit rechnen, dass der Code nicht mehr im selben Thread läuft, wie vor ein paar Zeilen.

Ich hab also nichts gegen die genannten Klassen wie Semaphore/Slim oder ReaderWriterLock/Slim, aber die gehen alle davon aus, dass der Thread zwischen Begin und Ende des gelockten Codes sich nicht mehr ändert.
Klassen wie ConcurrentBag oder ConcurrentCollections synchronisieren nur den Aufruf einer einzelnen Methode, aber sobald ich den Zugriff über mehrere Aufrufe hinweg sperren möchte, versagen sie.
private Nachricht | Beiträge des Benutzers
witte
myCSharp.de - Member



Dabei seit:
Beiträge: 958

beantworten | zitieren | melden

* ein SemaphoreSlim verlangt nicht dass der freigebende Thread derselbe wie der sperrende Thread ist.
* was ist mit ConfigureAwait?
private Nachricht | Beiträge des Benutzers
T-Virus
myCSharp.de - Member



Dabei seit:
Beiträge: 1768
Herkunft: Nordhausen, Nörten-Hardenberg

beantworten | zitieren | melden

@Palladin007
Bei Methoden wie ToListAsync wird häufig der ToList Aufruf nur per Task.Run gewrappt.
Es gibt aber auch Fälle, wo eben die gesamte Verarbeitung bei Async Methoden neu implementiert werden muss.
Hängt also von der jeweiligen Implementierung ab!

Dein Fall dürfte von .NET nicht abgedeckt sein bzw. wäre mir solch eine Collection nicht bekannt.
Ich würde vermutlich eine Wrapper Klasse bauen, die deine expliziten Zugriffe dann über ein eigenes Lock System löst.
Dein Beispielcode geht da schon in die richtige Richtung.
Das dürfte aber von der Umsetzung her nicht einfach werden und ggf. auch Fehleranfällig sein.
Den um das Thread Locking wirst du schon durch die Tasks nicht herum kommen.
Intern wirst du also auch mit einer ConcurrentCollection arbeiten müssen, sonst musst du nebem deinem expliziten Locking noch die Thread Locks auf die Collection regeln, was doppelter Aufwand wäre.

T-Virus
Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1411
Herkunft: NRW

Themenstarter:

beantworten | zitieren | melden

Dass es eine solche Liste nicht gibt, hab ich mir schon gedacht ^^

Ich hatte gehofft, auf sowas wie eine Monitor-Klasse gehofft, die unabhängig vom Thread arbeitet, sondern nur mit einem Objekt als Key. Entweder das Objekt ist als "In Benutzung" markiert oder eben nicht, völlig egal, in welchem Thread der Code gerade läuft.

Damit könnte ich mir dann alles selber bauen:

private object _lockKey = new object();

public void Add(T item)
{
    try
    {
        Monitor2.Lock(_lockKey);

        _innerList.Add(item);
    }
    finally
    {
        Monitor2.Release(_lockKey);
    }
}

public IDisposable TakeLock()
{
    Monitor2.Lock(_lockKey);

    return new DelegateDisposable(() => Monitor2.Release(_lockKey));
}

Das wäre nicht Mal sehr kompliziert und ließ sich beliebig auf jeden anderen Anwendungsbereich erweitern.

Zitat
ein SemaphoreSlim verlangt nicht dass der freigebende Thread derselbe wie der sperrende Thread ist.
Stimmt - hatte ich nicht ganz auf dem Schirm.
Das setzt aber voraus, dass ich eine zweite List-Klasse bauen muss, die man anstelle des Originals benutzen muss, da ich in diesem Task nicht die alten Methoden (die jeweils mit Wait beginnen und Release enden) aufrufen darf.
Es wäre eine Lösung - zumindest theoretisch.
Zitat
was ist mit ConfigureAwait?
Ich kann nicht zwingend davon ausgehen, dass es einen SynchronizationContext gibt, das wäre also sinnlos.
private Nachricht | Beiträge des Benutzers