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

  • »
  • Portal
  • |
  • Mitglieder
Beiträge von gfoidl
Thema: Fehler The server mode SSL must use a certificate with the associated private key bei SslStream.AuthenticateAsServer
Am im Forum: Web-Technologien

Hallo,

jetzt zeigt sich warume es wichtig mit möglichst genauen Begriffen zu arbeiten ;-)

Zitat
Das heisst das vorhandene Zertifikat (mit CERTIFICATE und PRIVATE KEY Abschnitt) kann ich als Client-Zertifikat nehmen
"Client-Zertifikat" ist hier undeutlich und je nach dem was genau gemeint ist hat ClaraSoft recht od. auch nicht.

In Bezug auf SSL (lässt sich vom Thema ableiten) hat ClaraSoft recht.
Der Server braucht den public und private key damit er "verschlüsseln" kann. Der Client hingegen braucht vom Server nur den public key.
TCPListener ist server-seitig, daher klappte es vermutlich nicht, da kein private key vorhanden ist.

Ist bei "Client-Zertifikat" jedoch der Zusammenhang "Authentifizierung per Client-Zertifikat" (da auch mit SSL / TLS zusammenhängt) so benötigt der Client public + private key, aber seine eigenen! Und nicht jene vom Server.

Grundsätzlich sollten die private Schlüssel (keys) nur auf der Seite bleiben zu der sie gehören -- daher auch "private".
D.h. Client private key kennt der Server nicht und umgekehrt. Alles andere ist absolut unsicher (und fahrlässig).


mfG Gü

Thema: Fehler The server mode SSL must use a certificate with the associated private key bei SslStream.AuthenticateAsServer
Am im Forum: Web-Technologien

Hallo Kriz,

bei X509Certificate2.CreateFromCertFile muss hierfür ein Zertifikat mit privatem Schlüssel angegeben werden. Oder der private Schlüssel wird als extra Argument bei dieser Methode angegeben.

Für die Serverseite sind public und private key nötig, andernfalls kann SSL nicht funktionieren.

mfG Gü

Thema: MemoryLeak "behebt" sich selbst durch Memory-Snapshot
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

GC.Collect bitte nur als Notlösung betrachten, da dies die interne Arbeit und Tuning vom GC durcheinander bringt.

PerfView ist mächtig, aber nicht trivial und intuitiv.
Wenn du willst und es gestattet ist, so kann ich mir die Dumps einmal anschauen und dann eine grobe Art Anleitung dazu erstellen.
Aber ich muss bei PerfView auch immer wieder "reinkommen", da ich es recht schnell wieder vergesse ;-)

mfG Gü

PS: aber erst am Montag, übers WE bin ich nicht da.

Thema: MemoryLeak "behebt" sich selbst durch Memory-Snapshot
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,


GC.Collect();
GC.WaitForPendingFinalzers();
GC.Collect();
unter Angabe der max. Geneartion räumt den Speicher ordentlich auf.


mfG Gü

Thema: MemoryLeak "behebt" sich selbst durch Memory-Snapshot
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

Zitat
.NET Memory Profiler einen Snapshot erstelle, sind die überflüssigen Instanzen weg und der Speicher freigegeben.
Die meisten dieser Profiler führen eine kompletten GC durch (also inkl. Gen-2 und LOH) und haken sich in den Informationsstrom vom GC rein um so an die Ojekte zu kommen.
Genau dieses Verhalten hast du beobachtet.

Versuch dich einmal an PerfView, das bietet i.d.R. mehr Infos / Möglichkeiten. Aber die UX ist sehr bescheiden...

mfG Gü

Thema: Speicherverbrauchs Ideen für C# Applikation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo jogibear9988,

mach ein Speicherabbild und analysiere das. Dann siehst du genau welcher Objekt am größten ist, etc.
Das geht am einfachsten mit dem dotnet-dump Tool.

Danach kannst du überlegen wie Objekte geteilt werden, etc.
So pauschal ohne den Code zu kennen, kann ich keine konkrete Hilfe geben.

mfG Gü

Thema: Dictionary wird Exception beim Einfügen vielen Datensätze
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Christoph K. ,

Zitat
ein RAM-Cache benötigt wird, der entsprechend viele Objekte in einem Dictionary beinhalten soll.
Ist Cache in-memory ein Möglichkeit?
Das wären .NET-Bordmittel und genau für solche Szenarien getrimmt.

mfG Gü

Thema: Dictionary wird Exception beim Einfügen vielen Datensätze
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Christoph K. ,

Zitat
die Exception "Die Arraydimensionen haben den unterstützten Bereich überschritten."
Die Exception hat auch einen Typ. Schau dir den an, so weißt du worum es geht.
Also ob es OutOfMemory, IndexOutOfRange, etc. ist. Der Text alleine ist schön, aber für eine Diagnose ist der Typ wichtiger.
Zitat
auf dem Computer ist noch genügend freien RAM vorhanden.
Bei der OutOfMemoryException geht es v.a. darum ob der GC einen zusammenhängenden Speicherbereich verwenden / finden / vom OS verlangen kann, der groß genug ist um das Objekt (hier beim Dictionary ein internen Array) halten zu können. Ist das nicht der Fall -> OOM.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

Zitat
Nun kann es aber sein, dass die arbeitende Instanz aus irgendeinem Grund ... nicht mehr existiert, das Lock vorher aber nicht korrekt freigegeben wurde.
Dazu ist der Releaser ja mit using (bzw. try-finally-Dispose) versehen. Somit wird auch im Fehlerfall der Lock korrekt verlassen.
Zitat
Durch die WeakReference würde dieser Deadlock irgendwann (indirekt durch den GC) wieder freigegeben werden.
Wahrscheinlicher ist aber, dass dadurch jemand Zugriff zum Lock hat der nicht sollte -- siehe oben.
Statt der WeakReference, die eben nicht deterministisch ist, wäre eine Art Deadlock-Detection möglich. Schauen wie viele beim Warten vor dem Lock sind und wie viele im Lock sind. Wenn da nichts passiert (eine bestimmte Zeit) so mag das als Indiz für den Deadlock verwendet werden, der dann entsperrt wird.

Od. weniger kompliziert: eine automatische Entsperrung nach einer bestimmten Zeit. Wenn z.B. die Arbeiten im geschützen Gebiet X Sekunden dauern, so wird das Ticket automatisch noch 2X entfernt. Wird der Lock verlassen, so den Timer zurücksetzen, etc.

Dann ist das Verhalten wenigsten deterministisch und keine Bug-Quelle.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

jetzt hab ich deine Antwort vorhin übersehen...

Zitat
aber hier ist es ja inhaltlich und dank nullable reference types offensichtlich, dass kein null übergeben werden darf.
Eben nur "darf". Das hindert aber niemanden dennoch null zu übergeben -- v.a. wenn man nicht weiß woher das Objekt kommt.
Unabhängig von Nullability Annotations soll / muss bei public APIs auf null geprüft werden.
Das ist (leider) ein Missverständnis dieses Sprachfeatures. Siehe dazu auch die Diskussion in https://github.com/dotnet/docs/pull/28890#discussion_r841062932.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

mir erschließt sich kein Einsatz der WeakReference. Ich halte das sogar für gefährlich und als Bug-Quelle -- das gehört mMn wieder raus.

Warum?
Folgendes Szenario: Lock wurde mit ticket1 betreten, somit ist er für ticket2, etc. versperrt.
Im durch den Lock geschützten Abschnitt passiert jetzt eine Menge, so dass u.a. ein GC durchgeführt wird und der Target der WeakReference wird null. Dadurch ergibt dann state.TryGetTicket(out object? stateTicket) false und es wird versucht in den Channel zu schreiben, da ja klappen wird. D.h. ticket2 bekommt Eintritt zum Lock währen dieser Abschnitt eigentlich noch durch ticket1 geschützt sein sollte.

Od. gibt es einen konkreten Fall wo so ein Verhalten sinnvoll ist?
Das lässt sich äußerst schwer nachvollziehen und erst recht nicht vorhersagen.
Wenn das Verhalten drin bleiben sollte, so sollte auch Diagnose-Unterstützung (EventLog) dazu welche meldet dass ein GC passierte und das Ticket abgeräumt wurde.
Das Ganze wird dadurch aber eher ein Ungetum für etwas wofür ich keinen Einsatzbereich sehe.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo,

Zitat
Welches unkontrollierte Verhalten meinst Du?
Wenn kein Task mehr eine Referenz auf das Ticket hat, gibt es doch auch keinen Task mehr, der etwas damit tun wollen könnte, oder?
Bei der WeakReference kann der GC das Target-Objekt abräumen wenn er glaubt es sei richtig. Ob dies aber für die Synchronisierung richtig ist kann der GC nicht wissen. Daher ist das Verhlaten vom Lock nicht mehr vorhersehbar und kann zu Bugs führen. Es sei denn das ganze Programm ist dafür ausgelegt bzw. es stellt kein Problem für die Geschäftslogik dar wenn auf einmal andere Ticket bearbeitet werden können.
Zitat
Aber woher weiß ich, was viel Maschinencode erzeugt?
Selbst heruasfinden. Entweder einen JIT-Dump ansehen (nur den erzeugten Maschinencode), das geht recht einfach mit https://sharplab.io/ und anderen offline Tools (da nehme ich ganz gerne bei BenchmarkDotNet den DisassemblyDiagnoser).
Hier hat aber ein Blick in den Quellcode der Implementierung gereicht um das abschätzen zu können. https://source.dot.net/ ist da ganz praktisch.
Zitat
geben die bei ticket=null auch false zurück
Diesen Fall würde ich als "undefiniert" für die Synchronisation sehen und daher eine NullReferenceException werfen. "Fail early" ist ein gutes Paradigma, denn so kann unterschieden werden ob tatsächlich null übergeben wurde od. ob der lock einfach nicht betreten werden kann.
Zitat
Hätte den Vorteil, dass eine besser Verständliche TimeoutException geworfen wird, anstelle einer OperationCanceledException.
Was meinst Du dazu?
Nachteil ist, dass dadurch wieder eine StateMachine nötig wird.
Gute Idee, finde ich besser als wenn nur die OperationCanceledException geworfen wird.
Im Exception-Filter würde es aber reichen when (!cancellationToken.IsCancellationRequested) zu prüfen, denn sonst kann es eh nirgends herkommen.
Wegen der State-Machine sehe ich hier keine relevanten Nachteile, da eh schon genug Code produziert wird, da fällt das eher nicht mehr in Gewicht.

mfG Gü

Thema: Warum ist die SHA256 Klasse abstract?
Am im Forum: Grundlagen von C#

Hallo,

Zitat
Einen konkreten Grund, warum sie das Factory-Pattern verwendet haben, weiß ich aber nicht. ... Was der Grund für dieses ungewöhnliche Vorgehen ist, würde mich aber auch interessieren.
Hat v.a. mit den verschiedenen Plattformen (Windows, Unix-artige, MacOs) und der Entstehungsgeschichte von .NET (Core) zu tun und da mit diesem Pattern die konkreten Implementierungen web-abstrahiert werden können. Somit war/ist es möglich auf unix-artigen System OpenSSL zu verwenden, während bei Windows deren Crypto-APIs verwendet werden. Weiters ist es somit möglich die Implementierungen zwischen managed und native zu verschieben ohne dass sich für einen Konsumenten dieser Typen etwas ändert.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

was noch fehlt sind null-Checks fürs übergebene Ticket.

Zitat
Das Ticket wird jetzt als WeakReference gespeichert, wenn die Referenz verloren ist, gilt der State automatisch als freigegeben
Das ist aber nicht mehr sehr deterministisch und kann schnell zu unkontrolliertem Verhalten führen.
Zumindest sollte das konfigurierbar sein, z.B. durch ein TicketBehavior-Argument. Somit würde es dann zwei (od. je nachdem wieviele solcher Behaviors zur Verfügung stehen) spezielle State-Klassen geben (mit gemeinsamer abstrakter Basis, so dass die konkreten Typen sealed sein können)

Als Überladung für das Timout würde ich nur (mehr) TimeSpan anbieten. Die int-Überladung somit weg, denn das kann jeder Benutzer selbst erstellen.
Den Trend sehe ich auch bei neuen .NET-APIs.

Kleiner Perf-Tipp:


public ValueTask<Releaser> EnterAsync(object ticket, TimeSpan timeout, CancellationToken cancellationToken = default)
{
    return timeout == TimeSpan.Zero
        ? EnterAsync(ticket, cancellationToken)
        : WithTimeout(ticket, timeout, cancellationToken);

    ValueTask<Releaser> WithTimeout(object ticket, TimeSpan timeout, CancellationToken cancellationToken)
    {
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

        timeoutCts.CancelAfter(timeout);

        return EnterAsync(ticket, timeoutCts.Token);
    }
}
Ist schneller zur Laufzeit ;-)
Das Erstellen der CancellationTokenSource erzeugt eine Menge (Maschinencode), daher wird EnterAsync eine recht große Methode, welche dann ihrerseits nicht mehr inlined wird bzw. werden soll, da einfach zu groß.
So wird die Methode aufgeteilt in eine lokale Funktion, so dass EnterAsync selbst klein bleibt und eher sicher vom JIT inlined wird.
Zitat
Ein normales AsyncLock erlaubt meine ich kein mehrfaches Enter, oder irre ich mich?
Ich sprach auch nicht vom "AsyncLock", sondern vom "async lock" (also den selbst erstellten).

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

bzgl. Lock <-> Monitor hast du recht. Wenns Monitor genannt würde, so sollte auch ein Pulse, etc. dabei sein. Daher ist wohl doch Lock-Suffix passender.

Zitat
Eine zweite Klasse die die gleichen Methoden anbietet, nur ohne Ticket.
Das wäre dann ein normaler async lock?
Wie da jetzt das AsyncLocal<object> ins Spiel kommt erschließt sich mir nicht.
Die Klasse kann ja einfach an den TicketLock weiterdelegieren, wobei das Ticket intern erstellt wird.

Zitat
Auf dem Handy habe ich noch nie Code gelesen - aber die Frage "Wer tut sowas?" kann ich mir vermutlich sparen
V.a. Code / PRs auf GitHub schau ich mir so auch an.


mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

zum Namen: Ticket gefällt mir ganz gut. Statt TicketLock tendiere ich zu TicketMonitor, da Monitor mehr mit Überwachen zu tun hat als mit Verschließen. Nur so vom Gefühl her.

Zitat
auch zum Stil ^^
Wenn du schon fragst: ich mag die var nicht, wenn nicht direkt ersichtlich ist welcher Typ es ist. Z.B.
Zitat


if (TryEnter(ticket, out var releaser))
    return releaser;
Welcher Typ releaser ist erschließt sich nur aus dem Kontext, dem gefolgt werden muss. In VS mit Intellisense gehts, aber wenn nur der Text vom Code vorhanden ist kann das lästig werden.

Ähnlich bei
Zitat


var success = TryEnter(ticket);
Tipp-Ersparnis kanns nicht sein, v.a. mit Intellisense ;-)
Es ist schon naheliegend dass success vom Typ bool ist, aber eindeutig lässt sich das nur beantworten wenn die Methode TryEnter bekannt ist.
Ich würde da immer bool direkt hinschreiben. Das vermeidet auch potentielle Fehler, falls (irgendwann) der Rückgabetyp von TryEnter geändert werden sollte, wie z.B. in eine Enum


public enum EnterStata
{
    Success,
    LockHeldByOtherTicket,
    GenericEnterFailure     // in Memoriem an die GDI+ Zeiten ;-)
}
Außer bei anonyment Type und vllt. Linq gibt es seit C# 9 (mit "target typed new") für mich keinen Grund var zu verwenden.
Zitat


releaser = success
    ? new(this, ticket)
    : default;
Auch hier würde ich gerne sehen welche Instanz da erstellt wird. Das target typed new ist nett -- und ein super Ersatzt für var -- aber es sollte damit nicht übertrieben werden.


var foo0 = new Foo();   // OK, aber mit target typed new nicht nötig
Foo foo1 = new();       // Finde ich besser, da wir von links nach rechts lesen und so der Typ sofort klar ist
Falls sonst der Typ nicht direkt klar ist, so sollte dieser unbedingt explizit angegeben werden. Außer bei, wie oben schon erwähnt, anonyment Typen (da gehts nicht anders) und ev. bei Linq (da kann es u.U. mehr verwirren als helfen).

V.a. wenn viel Code gelesen wird (und teilweise auf dem Handy) ist das ungemein praktisch.

Ansonsten passt dein Code schon. Das Aufteilen in Methoden, auch in Hinblick auf die TryEnter-Methoden, ist praktisch.

Vllt. sollte noch ein Überladung mit Timeout, wie im ganz ursprünglichen Code von dir, mit rein. Das könnte per verlinkter CancellationTokenSource durchgeführt werden, indem per CancellationTokenSource.CreateLinkedTokenSource eine neue CTS erstellt wird, welche dann mit cts.CancelAfter spätestens beim Timeout getriggert wird.
Hinweis: wenn eine CTS mit einem Timeout erstellt wurde, so muss diese unbedingt Disposed werden, da sonst die Timer-Queue (inter in .NET) wächst (memory leak).

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

ich hatte die gleiche Überlegung wie dannoe. Dieser Fall dürfte nie eintreten, sofern Enter auch richtig implementiert ist.

Im Allgemeinen hast du aber mit dem "double check lock" recht. Schnelle Prüfung, dann im lock nochmal Prüfen um zu Schauen ob eh durch kein Race sich der Zustand verändert hat.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

Zitat
Müsste das nicht durch das "??=" umgangen werden?
Ups, das hatte ich nur alt = gelesen. Sorry. Da hast du recht, das passt und wegen der Synchronisierung gibt es da auch kein Race.
Zitat
auf den gleichen Task warten.
...denn die AsyncSemaphore-Implementierung ... nutzt ja auch eine Queue
Task kann ja mehrfach erwartet werden, daher passt das schon. Bei ValueTask geht das i.d.R. nicht bzw. ist es im Allgemeinen besser den VT nur 1x zu erwarten.
Vllt. "korrekter" wäre es mit einer Liste (od. Queue) für die wartenden Tasks, da es im Grunde ja verschieden sind -- für jeden wartenden Aufrufer einer.

Gestern hab ich auch einen Versuch unternommen mit ValueTask (IValueTaskSource) ohne Channel. Wurde aber schnell sehr komplex, so dass das wieder verworfen wurde, da eben die Channel hier schon alles bietet was benötigt wird.

Unabhängig davon frag ich mich schon ob es nicht ein passendes Konstrukt dafür schon gibt -- v.a. mit Namen, denn es gibt ja (unabhängig von Programmiersprache) mehr od. weniger überall die gleichen Primitiven (die oft auf Dijkstra zurückgehen).

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

Zitat
und es tut ja auch nicht weh
Ist aber trotzdem falsch ;-)

Noch zwei Dinge zu deinem Code:
Zitat


if (ValidateAccess(obj))
{
    if (_obj?.IsAlive == false)
        _count = 0;

    _count++;
    _obj = new(obj);

    release = new(this, obj);
    waitTask = null;
    return true;
}
Würde ich als


if (ValidateAccess(obj))
{
    if (_obj?.IsAlive == false)
    {
        _count = 1;
        _obj = new(obj);
    }
    else
    {
        _count++;
    }

    release = new(this, obj);
    waitTask = null;
    return true;
}
schreiben. Das spart die wiederholte Allokation der WeakReference und _count kann gleich auf 1 gesetzt werden.
Zitat


else
{
    _waiter ??= new();

    release = default;
    waitTask = _waiter.Task;
    return false;
}
Da ist ein (latenter) Bug. Wenn TryEnter mit einem anderen obj aufgerufen wird als bisher in der WeakReference gehalten so wird jedesmal eine neue TaskCompletionSource erstellt. Bereits von der TaskCompletionSource (TCS) erzeugte Tasks können so nie komplettiert werden und das ist Fehlverhalten.
Um das zu korrigieren würde eine Liste mit den TCS benötigt werden. Das schaut den für die Allokationen gar nciht mehr gut aus und macht es doch recht kompliziert. Daher auch die Channels, da diese das ähnliche Problem intern recht elegant, aber nicht-trivial, lösen.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

(Teil 3)


//-----------------------------------------------------------------------------
// Keine Ahnung ob der Name treffend ist.
public sealed class ReEntryLock
{
    // Ein Channel ist hier praktisch, da er die interne Synchronisation und Signalisierung
    // der Tasks übernimmt.
    // Hier verwendet wir einen Channel mit Kapazität 1, da nur mit dem gleichen userState-Objekt
    // gleichzeitig eingetreten werden darf.
    private readonly Channel<State> _channel = Channel.CreateBounded<State>(new BoundedChannelOptions(1));

    // Damit wir wenig Allokationen haben, cachen wir das State-Objekt.
    private readonly State _state = new();
    //-------------------------------------------------------------------------
    // Hier hab ich es EnterAsync genennt, da wegen mehrfachem Eintritt das besser passt
    // als WaitAsync.
    public async ValueTask<Releaser> EnterAsync(object userState, CancellationToken cancellationToken = default)
    {
        while (true)
        {
            // Dieser lock könnte fein-granularer geschrieben werden. Aber dann müsste bei jeder
            // _channel.{Reader,Writer}.TryXYZ-Methode auf das Ergebnis reagiert werden. Somit
            // würde der Code nur unnötig verkompliziert. Daher lieber einfacher (und auch sicherer)
            // mit dem "äußeren" lock.
            lock (_state)
            {
                // Wir schauen ob ein Element im Channel ist.
                // Falls ja, so wurde der Lock schon betreten, daher müssen wir schauen
                // ob es das gleiche userState-Objekt ist um erneut betreten zu dürfen.
                if (_channel.Reader.TryPeek(out State? stateInChannel))
                {
                    if (ReferenceEquals(userState, stateInChannel.UserState))
                    {
                        // Es ist das gleiche Objekt, daher Zähler erhöhen und eintreten lassen.
                        stateInChannel.IncrementEnteredCount();
                        return new Releaser(this, userState);
                    }
                }
                // Es ist kein Element im Channel, daher eintreten lassen.
                else
                {
                    bool written = _channel.Writer.TryWrite(_state);
                    Debug.Assert(written);

                    _state.Reset(userState);
                    return new Releaser(this, userState);
                }
            }

            // Bleibt nichts anderes übrig als zu warten bis wieder Platz im Channel ist.
            await _channel.Writer.WaitToWriteAsync(cancellationToken).ConfigureAwait(false);
        }
    }
    //-------------------------------------------------------------------------
    public void Release(object userState)
    {
        Debug.Assert(_channel.Reader.Count > 0);

        // Wir schauen ob das userState-Objekt zum Objekt im Channel passt.
        // Falls ja, so dekrementieren wir den Zähler vom State im Channel.
        // Ist der Zähler 0, so wird der Channel geleert und ist somit wieder frei.
        //
        // - Ist der Channel leer, so passiert nichts.
        // - Ist es ein anderes userState-Objekt so passiert nichts.
        if (_channel.Reader.TryPeek(out State? stateInChannel))
        {
            lock (_state)
            {
                if (ReferenceEquals(userState, stateInChannel.UserState)
                && stateInChannel.DecrementEnteredCount() == 0)
                {
                    bool freedChannel = _channel.Reader.TryRead(out State? stateRead);

                    // Nur zur Sicherheit und da ich Debug.Assert normal gerne als "Verträge"
                    // Code verwende, da sonst so gut wie keine Kommentare vorhanden sind.
                    Debug.Assert(freedChannel);
                    Debug.Assert(ReferenceEquals(stateRead!.UserState, stateInChannel.UserState));
                }
            }
        }
    }
    //-------------------------------------------------------------------------
    // Eine struct wäre schön, geht hier aber nicht, da sie mutable sein muss
    // für das Inkrementieren vom Zähler, aber beim Schreiben in den Channel
    // wird eine Kopie (wegen Werttyp) erstellt und das Inkrementieren ist sinnlos.
    // Daher muss es ein Referenztyp sein.
    [DebuggerDisplay("EnteredCount: {_enteredCount,nq}, UserState: {UserState,nq}")]
    private class State
    {
        private int _enteredCount;
        public object? UserState { get; private set; }
        //---------------------------------------------------------------------
        public void Reset(object userState)
        {
            _enteredCount  = 1;
            this.UserState = userState;
        }
        //---------------------------------------------------------------------
        public int IncrementEnteredCount() => ++_enteredCount;
        public int DecrementEnteredCount() => --_enteredCount;
    }
    //-------------------------------------------------------------------------
    // Ist nicht nötig, aber so kann per using eleganter gearbeitet werden,
    // anstatt manuell Release aufrufen zu müssen (das ist in deinem Code ja auch schon so).
    public readonly struct Releaser : IDisposable
    {
        private readonly ReEntryLock _parent;
        private readonly object      _userState;
        //---------------------------------------------------------------------
        internal Releaser(ReEntryLock parent, object userState) => (_parent, _userState) = (parent, userState);
        //---------------------------------------------------------------------
        public void Dispose() => _parent?.Release(_userState);
    }
}
Natürlich ginge das ohne Channel auch, aber der Channel nimmt uns sehr viel komplizierte Arbeit ab.
Die Tasks, welche warten müssen, könnten auch per TaskCompletionSource<T>, IValueTaskSource, etc. erstellt werden. Aber wie denen dann signalisiert wird dass es weiter geht ist doch recht aufwändig -- v.a. wenns robust und sicher gemacht werden soll. Je nachdem wo die Anwendung laufen soll, kann auch noch der ExecutionContext usw. ins Spiel kommen. Das will ich mir und dir ersparen, daher sind die Channels verwendet worden.
Zitat


[MaybeNullWhen(false)] out Releaser release
Releaser ist ein Werttype, kann also nicht null sein, daher ist diese Annotation umsonst.

mfG Gü

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

(Teil 2)

Zurück zu deinem Problem: das lässt sich mit Channel ganz gut abbilden -- (für mich) der Vorteil dabei ist, dass ValueTasks im Spiel sind und v.a. bei Channels sind diese mittels IValueTaskSource umgesetzt, so dass diese Tasks selbst keine zusätzlichen Allokationen haben (beim await die AsyncStateMachineBox, welche die lokalen Variablen, etc. hält, gibt es aber auch hier, geht nicht anders).
Nur dass du gedanklich das Problem da auch invertieren musst. Etwas fertiges kann ich nicht nennen, aber ein Beispiel (mit etlichen Kommentaren).
Hab die Klasse ReEntryLock genannt, keine Ahnung ob es bessere Bezeichnungen gibt -- Namensgebung ist ziemlich das schwerste ;-)


//#define PRINT_ID

using System.Diagnostics;
using System.Threading.Channels;

const int N                       = 5;
using CancellationTokenSource cts = new();
ReEntryLock reEntryLock           = new();
List<Task> tasks                  = new(capacity: N);

Console.WriteLine("Start");

tasks.Add(Do(new UserState(1, ConsoleColor.Cyan)   , cts.Token));
tasks.Add(Do(new UserState(2, ConsoleColor.Magenta), cts.Token));
tasks.Add(Do(new UserState(3, ConsoleColor.White)  , cts.Token));

await Task.WhenAll(tasks);
Console.WriteLine("Done");
//-----------------------------------------------------------------------------
async Task Do(UserState state, CancellationToken cancellationToken)
{
    await Task.Yield();

    Print(state, ConsoleColor.Red, "before signal");
    using (await reEntryLock.EnterAsync(state, cancellationToken))
    {
        Print(state, ConsoleColor.Green, "behind signal");

        for (int i = 0; i < 3; ++i)
        {
#if PRINT_ID
            using (await reEntryLock.EnterAsync(state, cancellationToken))
            {
                Thread.Sleep(Random.Shared.Next(750, 1000));        // für Demo kein Task.Delay
                Console.Write(state.Id);
            }
#else
            Print(state, ConsoleColor.Red, "before signal");
            using (await reEntryLock.EnterAsync(state, cancellationToken))
            {
                Print(state, ConsoleColor.Green, "behind signal");
                Thread.Sleep(Random.Shared.Next(750, 1000));        // für Demo kein Task.Delay
            }
            Print(state, ConsoleColor.Yellow, "released signal");
#endif
        }
#if PRINT_ID
        Console.WriteLine();
#endif
    }
    Print(state, ConsoleColor.Yellow, "released signal");
}
//-----------------------------------------------------------------------------
[DebuggerStepThrough]
void Print(UserState state, ConsoleColor color, string message)
{
#if !PRINT_ID
    lock (tasks)
    {
        Console.ForegroundColor = state.Color;
        Console.Write($"Thread-Id: {Environment.CurrentManagedThreadId}, Id: {state.Id}, ");
        Console.ForegroundColor = color;
        Console.WriteLine($"{message}");
        Console.ResetColor();
    }
#endif
}
//-----------------------------------------------------------------------------
public record UserState(int Id, ConsoleColor Color);

Im nächsten Teil der eigentliche Teil, auf den ich schon die ganze Zeit hinaus will...

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007 (Teil 1),

habs für dich mit ein paar Code-Beispielen mehr versehen (musste den Beitrag aufteilen, da zuviel Zeichen vorhanden sind) ;-)

Zitat
das umgekehrte Verhalten, sodass 0 quasi ein Freifahrtschein für alle ist und der erste, der "gewinnt", darf danach exklusiv arbeiten, bis es wieder auf 0 steht.
So hab ich das eigentlich eh verstanden.
Vllt. hab ich mich im vorigen Kommentar unglücklich ausgedrückt, denn das von dir zitierte war nur für die Channel als Ersatz für die Semaphore gedacht.

Hier als Beispiel:


using System.Threading.Channels;

const int N = 3;

using CancellationTokenSource cts                       = new();
MySemaphareMadeWithChannels mySemaphareMadeWithChannels = new(2);
List<Task> tasks                                        = new(capacity: 3);

Print("Start");

tasks.Add(Do(cts.Token, ConsoleColor.Cyan));
tasks.Add(Do(cts.Token, ConsoleColor.Magenta));
tasks.Add(Do(cts.Token, ConsoleColor.White));

await Task.WhenAll(tasks);
Print("Done");
//-----------------------------------------------------------------------------
async Task Do(CancellationToken cancellationToken, ConsoleColor color)
{
    // Ohne Sync-Context und mit dem Standard-TaskScheduler wird die Continuation
    // einfach dem ThreadPool hinzugefügt.
    await Task.Yield();

    Print("before signal", color, ConsoleColor.Red);
    await mySemaphareMadeWithChannels.WaitAsync(cancellationToken);
    Print("behind signal", color, ConsoleColor.Green);

    // Für Demo ist Thread.Sleep geeigneter, da mit Task.Delay der Ausführungs-Thread
    // gewechselt werden kann.
    //await Task.Delay(Random.Shared.Next(750, 1000), cancellationToken);
    Thread.Sleep(Random.Shared.Next(750, 1000));

    mySemaphareMadeWithChannels.Release();
    Print("released signal", color, ConsoleColor.Yellow);
}
//-----------------------------------------------------------------------------
void Print(string message, ConsoleColor? colorForId = null, ConsoleColor? colorForMessage = null)
{
    if (colorForId is not null && colorForMessage is not null)
    {
        lock (tasks)
        {
            Console.ForegroundColor = colorForId.Value;
            Console.Write($"Thread-Id: {Environment.CurrentManagedThreadId,2}, ");
            Console.ForegroundColor = colorForMessage.Value;
            Console.WriteLine($"free spaces: {mySemaphareMadeWithChannels.FreeSpaces}, {message}");
            Console.ResetColor();
        }
    }
    else
    {
        Console.WriteLine($"Thread-Id: {Environment.CurrentManagedThreadId,2}, free spaces: {mySemaphareMadeWithChannels.FreeSpaces}, {message}");
    }
}
//-----------------------------------------------------------------------------
public sealed class MySemaphareMadeWithChannels
{
    private readonly Channel<int> _channel;
    //-------------------------------------------------------------------------
    public MySemaphareMadeWithChannels(int taskCount)
    {
        _channel = Channel.CreateBounded<int>(new BoundedChannelOptions(taskCount));

        for (int i = 0; i < taskCount; ++i)
        {
            _channel.Writer.TryWrite(42);
        }
    }
    //-------------------------------------------------------------------------
    public int FreeSpaces => _channel.Reader.Count;
    //-------------------------------------------------------------------------
    public ValueTask WaitAsync(CancellationToken cancellationToken = default)
    {
        ValueTask<int> readTask = _channel.Reader.ReadAsync(cancellationToken);

        // Falls synchron fertig, so kann die async-Statemachine, welche C# erstellt, vermieden werden
        if (readTask.IsCompleted)
        {
            // Der ValueTask kann aus einer IValueTaskSource erstellt worden sein. Diese muss
            // zurückgesetzt werden und das geschieht per Zugriff auf Result.
            // Mit GetAwaiter().GetResult() geschieht das Gleich, nur effizienter.
            readTask.GetAwaiter().GetResult();
            return ValueTask.CompletedTask;
        }

        return Core(readTask);

        static async ValueTask Core(ValueTask<int> task)
        {
            await task.ConfigureAwait(false);
        }
    }
    //-------------------------------------------------------------------------
    public void Release() => _channel.Writer.TryWrite(42);
}

Thema: Async Task-Synchronisation
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo Palladin007,

erstell dir eine eigene Datenstrukture welche für die Synchronisation zuständig ist.
Inter kann diese entweder via SemaphoreSlim arbeiten od. (was mir besser gefällt) mit Channel<T>s.

Bei der Variante mit den Channel<T> gibts die "Kapazitäten" vor und wenns 0 wird, so heißt es warten.
Da das alles in deiner Datenstruktur gekapselt ist, kannst du je nach übergebenen Objekt weiter entscheiden.

Der Vorteil von Channel<T> ist auch, dass diese ValueTask-basiert sind und somit bei vielen Vorgängen wenn keine "Contention" vorliegt performanter sind.
Grundsätzlich lässt sich das aber auch alles per Semaphore(Slim) erledigen.

mfG Gü

Thema: .txt Datei Filtern und die wichtigen daten extrahieren
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo LangWind,

super, wenn du eine Lösung gefunden hast. Noch idealer und im Sinne der Community wäre jedoch, wenn du hier die Lösung auch zeigen könntest.
Falls jemand ein ähnliches Problem hat, so kann er sich daran orientieren.

mfG Gü

Thema: .exe nur mit Lizenz öffnen
Am im Forum: Rund um die Programmierung

Hallo henrik1995,

da stimme ich Stefan.Haegele komplett zu.

Was soll den die Anwendung machen?
Wenns z.B. Berichte erstellt, so ist es "besser" wenn beispielsweise in der Fußzeile "Lizenziert für XYZ" steht. Das ist einfach zu bewerkstelligen und wenn "ABC" den Bericht weitergibt, so ist es wohl verwunderlich dass er mit "XYZ" annotiert ist.
Daher auch vorhin die Frage nach dem Anwendungsfall -- vllt. gibt es ja eine Lösung die sinnvoll erscheint.

Juristischer ausgedrückt (obwohl ich kein Jurist bin) ist die "Projekthyginie" vernüftiger als der Versuch einen Lizenzschutz per SW-Mechanismus zu erzwingen, der eh leicht auszuhebeln ist.

Ich denke dass jeder Programmierer (meist in der Anfangszeit seines Wirkens) einen ähnlichen Wunsch nach Lizenzierung des eigenen (Mini-) Programms hatte. Mit fortschreitender Erfahrung geht dieser Wunsch aber meist gegen 0, aus den erwähnten Gründen.

mfG Gü

Thema: .txt Datei Filtern und die wichtigen daten extrahieren
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo LangWind,

achte bei iText unbedingt auf die Lizenz.

Falls das PDF rein textuell aufgebaut ist, so kann auch z.B. mittels pdfgrep der Text extrahiert und anschließend von deinem C#-Programm geparst werden.
Da der Export rein textuell ist und gem. deinem Beispiel das recht kanonisch aufgebaut ist, sollte das gehen. Bei den Arbeitern ist der Fettgedruckte auch mit einem x versehen, so dass dies (beim Beispiel zumindest) eindeutig ist.

mfG Gü

Thema: .exe nur mit Lizenz öffnen
Am im Forum: Rund um die Programmierung

Hallo henrik1995 ,

Zitat
Eine License-Validierung beendet dann aber wieder den Prozess.
So verlockend das auch sein mag, das lässt sich relativ einfach umgehen / patchen, so dass dieser Schutz nicht wirklich wirksam ist.
Das ist per se keine Eigenheit von .NET / C#, sonder trifft auch auf native Anwendungen (wie jene in C/C++ geschrieben) zu.

Gibt es einen konkreten Anwendungsfall den du abdecken willst?
Vllt. gibt es dafür eine speziellere und sicherere Lösung.

mfG Gü

Thema: Sequentielles ausführen von Funktionen. Gibt es da ein Pattern?
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo ill_son,

als einfaches "Pattern":


public interface IStep
{
    Task ExecuteAsync(CancallationToken cancellationToken);
}

public class FirstStep : IStep
{
    // ...
}

public class SecondStep : IStep
{
    // ...
}

public class Manager
{
    private readonly List<IStep> _steps;

    public Manager(List<IStep> steps) => _steps = steps;

    public async Task RunAsync(CancallationToken cancellationToken)
    {
        foreach (IStep step in _steps)
        {
            try
            {
                await step.ExecuteAsync(cancellationToken);    // mit od. ohne ConfigureAwait -- abhängig von der Umgebung
                // Hier ev. Event feuern für Fortschritt od. mittels IProgress-Pattern das berichten
            }
            catch (Exception ex)
            {
                // Loggen od. was auch immer
                // Schleife kann hier auch mit break verlassen werden        
            }
        }
    }
}

So ist jeder Schritt eine eigene Klasse und kann separat implementiert und getestet werden.


mfG Gü

Thema: ByteArray in Object Convertieren
Am im Forum: Netzwerktechnologien

Hallo Loofsy,

Zitat
Ich habe ein Client Serversystem entwickelt
Was hast du denn (als größeres Ziel) vor? Client-Server Protokolle / Systeme wurden schon viele entwickelt, manche sind wieder verschwunden, andere haben sich etabliert.
Die Etablierten sind allesamt vielfach im Einsatz und somit auch sehr robust, wie z.B. HTTP-APIs, gRPC, etc. Je nach konkreter Anforderung können weitere Möglichkeiten in Betracht gezogen werden -- ob es rein Client/Server ist od. doch eher Pub/Sub, etc.

Selbst entwickeln würde ich so ein System eigentlich nur zu Lern-/Verständniszwecken od. wenn es so eine Nischenanwendung ist dass es wirklich kein vorhandenes System dafür gibt.

mfG Gü

Thema: Viren im Programmcode
Am im Forum: Grundlagen von C#

Hallo sane,

Zitat
Also besser keinen Code von anderen annehmen
So würde ich das nicht schlussfolgern. Besser ist es einen aktuellen und sicheren Editor (wie VS in aktueller Version) zu verwenden, damit kein Schaden angerichtet werden kann.

mfG Gü