So ein event Action<T>
von einer anderen Klasse meiner Anforderung ist meine Funktion Warten auf die erste Instanz zu haben und eine anderen Thread kommt, und behandelt es auf meinem Thread, die Wartezeit erlaubt durch Timeout oder CancellationToken
unterbrochen werden.Wie für ein einzelnes Ereignis in C# warten, mit Timeout und Löschung
Ich möchte eine generische Funktion erstellen, ich wiederverwenden können. Es ist mir gelungen, ein paar Optionen zu schaffen, die (denke ich) das tun, was ich brauche, aber beide scheinen komplizierter zu sein, als ich mir vorstellen kann.
Nutzungs
Nur klar zu sein, eine Probe die Verwendung dieser Funktion wie folgt aussehen würde, wo serialDevice
spuckt Ereignisse auf einem separaten Thread:
var eventOccurred = Helper.WaitForSingleEvent<StatusPacket>(
cancellationToken,
statusPacket => OnStatusPacketReceived(statusPacket),
a => serialDevice.StatusPacketReceived += a,
a => serialDevice.StatusPacketReceived -= a,
5000,
() => serialDevice.RequestStatusPacket());
Option 1-ManualResetEventSlim
Diese Option ist nicht schlecht, aber die Dispose
Handhabung der ManualResetEventSlim
ist chaotischer, als es scheint, wie es sein sollte. Es gibt ReSharper passt, dass ich auf modifizierte/entsorgte Dinge innerhalb der Schließung zugreifen, und es ist wirklich schwer zu folgen, so dass ich nicht einmal sicher bin, dass es korrekt ist. Vielleicht gibt es etwas, das ich vermisse, das das aufräumen kann, was meine Vorliebe wäre, aber ich sehe es nicht ohne weiteres. Hier ist der Code.
public static bool WaitForSingleEvent<TEvent>(this CancellationToken token, Action<TEvent> handler, Action<Action<TEvent>> subscribe, Action<Action<TEvent>> unsubscribe, int msTimeout, Action initializer = null)
{
var eventOccurred = false;
var eventResult = default(TEvent);
var o = new object();
var slim = new ManualResetEventSlim();
Action<TEvent> setResult = result =>
{
lock (o) // ensures we get the first event only
{
if (!eventOccurred)
{
eventResult = result;
eventOccurred = true;
// ReSharper disable AccessToModifiedClosure
// ReSharper disable AccessToDisposedClosure
if (slim != null)
{
slim.Set();
}
// ReSharper restore AccessToDisposedClosure
// ReSharper restore AccessToModifiedClosure
}
}
};
subscribe(setResult);
try
{
if (initializer != null)
{
initializer();
}
slim.Wait(msTimeout, token);
}
finally // ensures unsubscription in case of exception
{
unsubscribe(setResult);
lock(o) // ensure we don't access slim
{
slim.Dispose();
slim = null;
}
}
lock (o) // ensures our variables don't get changed in middle of things
{
if (eventOccurred)
{
handler(eventResult);
}
return eventOccurred;
}
}
Option 2-Polling ohne WaitHandle
Die WaitForSingleEvent
Funktion hier ist viel sauberer. Ich kann ConcurrentQueue
verwenden und brauche daher nicht einmal eine Sperre. Aber ich mag einfach nicht die Polling-Funktion Sleep
, und ich sehe es mit diesem Ansatz nicht herum. Ich möchte in einem WaitHandle
passieren anstelle eines Func<bool>
aufzuräumen Sleep
, aber die zweite ich, dass ich die ganze Dispose
Chaos haben wieder aufzuräumen.
public static bool WaitForSingleEvent<TEvent>(this CancellationToken token, Action<TEvent> handler, Action<Action<TEvent>> subscribe, Action<Action<TEvent>> unsubscribe, int msTimeout, Action initializer = null)
{
var q = new ConcurrentQueue<TEvent>();
subscribe(q.Enqueue);
try
{
if (initializer != null)
{
initializer();
}
token.Sleep(msTimeout,() => !q.IsEmpty);
}
finally // ensures unsubscription in case of exception
{
unsubscribe(q.Enqueue);
}
TEvent eventResult;
var eventOccurred = q.TryDequeue(out eventResult);
if (eventOccurred)
{
handler(eventResult);
}
return eventOccurred;
}
public static void Sleep(this CancellationToken token, int ms, Func<bool> exitCondition)
{
var start = DateTime.Now;
while ((DateTime.Now - start).TotalMilliseconds < ms && !exitCondition())
{
token.ThrowIfCancellationRequested();
Thread.Sleep(1);
}
}
Die Frage
ich für eine dieser beiden Lösungen nicht besonders kümmern, noch bin ich 100% sicher, dass jeder von ihnen 100% korrekt sind. Ist entweder eine dieser Lösungen besser als die andere (Idiomatik, Effizienz usw.) oder gibt es einen einfacheren Weg oder eine eingebaute Funktion, um das zu erfüllen, was ich hier tun muss?
Update: Beste Antwort bisher
Eine Modifikation der TaskCompletionSource
Lösung unten. Keine langen Verschlüsse, Schlösser oder irgendetwas benötigt. Scheint ziemlich einfach. Irgendwelche Fehler hier?
public static bool WaitForSingleEvent<TEvent>(this CancellationToken token, Action<TEvent> onEvent, Action<Action<TEvent>> subscribe, Action<Action<TEvent>> unsubscribe, int msTimeout, Action initializer = null)
{
var tcs = new TaskCompletionSource<TEvent>();
Action<TEvent> handler = result => tcs.TrySetResult(result);
var task = tcs.Task;
subscribe(handler);
try
{
if (initializer != null)
{
initializer();
}
task.Wait(msTimeout, token);
}
finally
{
unsubscribe(handler);
// Do not dispose task http://blogs.msdn.com/b/pfxteam/archive/2012/03/25/10287435.aspx
}
if (task.Status == TaskStatus.RanToCompletion)
{
onEvent(task.Result);
return true;
}
return false;
}
Update 2: Eine andere große Lösung
Es stellte sich heraus, dass BlockingCollection
Werke ConcurrentQueue
genau wie hat aber auch Methoden, um ein Timeout und Stornierungs Token zu akzeptieren. Eine nette Sache über diese Lösung ist, dass es aktualisiert werden kann, ein WaitForNEvents
ziemlich leicht zu machen:
public static bool WaitForSingleEvent<TEvent>(this CancellationToken token, Action<TEvent> handler, Action<Action<TEvent>> subscribe, Action<Action<TEvent>> unsubscribe, int msTimeout, Action initializer = null)
{
var q = new BlockingCollection<TEvent>();
Action<TEvent> add = item => q.TryAdd(item);
subscribe(add);
try
{
if (initializer != null)
{
initializer();
}
TEvent eventResult;
if (q.TryTake(out eventResult, msTimeout, token))
{
handler(eventResult);
return true;
}
return false;
}
finally
{
unsubscribe(add);
q.Dispose();
}
}
Es klingt, als ob Sie etwas wie 'AutoResetEvent' möchten. Hast du die Möglichkeit gesehen, es zu benutzen? –
@KendallFrey Ja, das scheint mich in die gleiche 'Dispose'-Verwirrung zu bringen, die' ManualResetEventSlim' hat, oder hattest du einen Weg darum herum? – lobsterism
Der Grund, dass Resharper sich beschwert, ist, weil es den Fluss nicht analysieren kann, da die Subscribe- und Abmeldeaktionen übergeben werden. Es kann aufhören, sich zu beklagen, wenn Sie das Ereignis (über Reflektion) übertrugen oder irgendwie den Fluss mehr verifizierbar machten. In jedem Fall halte ich persönlich die Responderwarnungen nicht hoch. –