2010-05-12 12 views
5

Ich habe geschrieben, was ich hoffe, ist eine leichte Alternative zur Verwendung der ManualResetEvent und AutoResetEvent-Klassen in C# /. NET. Der Grund dafür war eine Event-ähnliche Funktionalität ohne die Verwendung eines Kernel-Sperrobjekts.Leichte Alternative zu Manual/AutoResetEvent in C#

Obwohl der Code sowohl im Test als auch in der Produktion gut zu funktionieren scheint, kann es eine schwierige Aufgabe sein, diese Art von Ding für alle Möglichkeiten in Ordnung zu bringen und demnächst konstruktive Kommentare und Kritik von StackOverflow zu erbitten. Hoffentlich (nach Überprüfung) wird dies für andere nützlich sein.

Die Verwendung sollte ähnlich wie die Manual/AutoResetEvent-Klassen sein, wobei Notify() für Set() verwendet wird.

Hier geht:

using System; 
using System.Threading; 

public class Signal 
{ 
    private readonly object _lock = new object(); 
    private readonly bool _autoResetSignal; 
    private bool _notified; 

    public Signal() 
    : this(false, false) 
    { 
    } 

    public Signal(bool initialState, bool autoReset) 
    { 
    _autoResetSignal = autoReset; 
    _notified = initialState; 
    } 

    public virtual void Notify() 
    { 
    lock (_lock) 
    { 
     // first time? 
     if (!_notified) 
     { 
     // set the flag 
     _notified = true; 

     // unblock a thread which is waiting on this signal 
     Monitor.Pulse(_lock); 
     } 
    } 
    } 

    public void Wait() 
    { 
    Wait(Timeout.Infinite); 
    } 

    public virtual bool Wait(int milliseconds) 
    { 
    lock (_lock) 
    { 
     bool ret = true; 
     // this check needs to be inside the lock otherwise you can get nailed 
     // with a race condition where the notify thread sets the flag AFTER 
     // the waiting thread has checked it and acquires the lock and does the 
     // pulse before the Monitor.Wait below - when this happens the caller 
     // will wait forever as he "just missed" the only pulse which is ever 
     // going to happen 
     if (!_notified) 
     { 
     ret = Monitor.Wait(_lock, milliseconds); 
     } 

     if (_autoResetSignal) 
     { 
     _notified = false; 
     } 
     return (ret); 
    } 
    } 
} 
+1

Haben gebenchmarkt Sie diese gegen Manual/AutoResetEvents mit? Wie wichtig ist der Leistungsunterschied? – James

+0

Nein, ich habe noch nicht, da das primäre Ziel war, eine Nicht-Kernel-Handle/Ressource mit Event-Objekt zu machen. Ich werde versuchen, einige Tests einzurichten, danke. – sweetlilmre

+1

Die Implementierung eigener Threading-Primitive ähnelt der Implementierung eigener Krypto-Algorithmen. Wenn Sie kein Experte in der Domäne sind, werden Sie es vermasseln. Selbst wenn Sie ein Experte sind, können Sie es immer noch vermasseln. Tu es nicht. .NET 4 hat sowieso schon "leichte" Versionen, "ManualResetEventSlim" und verwandte Klassen. – Aaronaught

Antwort

4

Das von der Annahme arbeitet, dass Win32 Ereignisse teuer sind. Sie sind nicht, es gibt wenig, dass ich denken kann, das ist billiger als ein Ereignis. Ein wichtiger Hinweis, dass dies so ist, ist, dass die .NET-Designer entscheiden, dass es eine gute Idee ist, ein Win32-Ereignis zu verwenden, um MRE und ARE zu implementieren.

Die wahren Kosten für Ihren Ersatz sind die großen FUDs, die Sie erleben werden, wenn Sie ein Threading-Rennen haben und nicht wissen, was es verursacht.

+1

Nun, die Annahme ist, dass ARE und MRE ein Kernel-Ereignis unter der Haube verwenden, das teurer als ein Monitor wäre. Ich würde gerne falsch bewiesen werden :) – sweetlilmre

+0

Dieser Artikel von microsoft Details, die man schnell sind: http://msdn.microsoft.com/de-us/library/ms228964.aspx –

+0

Aus der Perspektive eines Kernel-Modus-Übergang in .NET, Win32-Ereignisse sind teuer, daher die leichten Ergänzungen in .NET 4 nach @Aaronaught oben beantworten. – sweetlilmre

1

Leider ist die korrekte Monitor-Implementierung ziemlich schwergewichtig angesichts der Win32-Synchronisationsgrundelemente. Mein anfänglicher Verdacht ist, dass "Sperren" bei der Ressourcennutzung schwerer wären als bei einem Ereignis (und wahrscheinlich auf einem Ereignis aufgebaut sind).

+0

Ich bin mir ziemlich sicher, dass das Schloss in keiner Weise von einem Event unter der Haube umgesetzt wird. IIRC Es gibt spezielle Thread-Strukturen, die speziell für die Thread-Locking-Funktion verwendet werden und diese leichter als Ereignisse machen. – sweetlilmre

+0

@sweetlilmre - Ich sah in der Rotor/SharedCCI-Implementierung, und sie verwenden einige Spinlock-Stil-Sperren, aber es kommt zu einem Ereignistyp, der vom Host bei Bedarf geliefert wird. –

+0

Ja, sie drehen sich selbst und benutzen ihre eigenen Syncblock-Bits. Die einzige Sache, die ich nicht gemunkelt habe, ist, wie sie den Windows-Scheduler wissen lassen. Haben Sie einen Code-Loc? –

1

Eine Möglichkeit, die Leistung von AutoResetEvent zu optimieren, besteht darin, den Status (signalisierte/nicht signalisierte) in Ihrer eigenen Variablen zu belassen. Bevor Sie also in den Kernel fahren und das Ereignisobjekt tatsächlich verwenden, können Sie einfach den Status Ihrer Anwendung überprüfen Variable und bleiben die ganze Zeit im Benutzermodus.
Ich habe eine demonstration dieses Konzepts vor ein paar Monaten gepostet.