Yani benim gereksinim, başka bir sınıf ve başka bir iş parçacığından gelen bir event Action<T>
ilk örnek için beklemek ve beklemeyi sağlayan iş parçacığım üzerinde işlemek için sahip olmaktır zaman aşımı veya CancellationToken
tarafından kesintiye uğratılır.C#, tek bir olay için zaman aşımı ve iptal ile nasıl beklenir
Yeniden kullanabileceğim genel bir işlev oluşturmak istiyorum. İhtiyacım olan şeyi (sanırım) yapan bir çift seçenek oluşturmayı başardım, ama her ikisinin de olması gerektiğini hayal ettiğimden daha karmaşık görünüyor.
Daha açıkçası, bu işlevin bir örnek kullanımı şu şekilde görünecektir Kullanımı,
serialDevice
ayrı iş parçacığı üzerinde olayları saçıyor:
var eventOccurred = Helper.WaitForSingleEvent<StatusPacket>(
cancellationToken,
statusPacket => OnStatusPacketReceived(statusPacket),
a => serialDevice.StatusPacketReceived += a,
a => serialDevice.StatusPacketReceived -= a,
5000,
() => serialDevice.RequestStatusPacket());
Seçenek 1-ManualResetEventSlim
Bu seçenek kötü değil, ancak ManualResetEventSlim
'un Dispose
kullanımı, olması gerektiği gibi göründüğünden daha karışıktır. ReSharper, kapağın içinde değiştirilmiş/bertaraf edilen şeylere erişebileceğimi uyarıyor ve gerçekten de takip edilmesi zor, bu yüzden doğru olduğundan emin değilim. Belki de bunu temizleyebilecek bir şeyim var, bu benim tercihim olabilir, ama ben bunu uygunsuz görmüyorum. İşte kod. Bir WaitHandle
burada WaitForSingleEvent
fonksiyonu olmadan
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;
}
}
Seçenek 2-yoklama daha temiz. ConcurrentQueue
kullanabiliyorum ve bu yüzden bir kilitlemeye bile gerek yok. Ancak, yoklama işlevi Sleep
'u beğenmiyorum ve bu yaklaşımla herhangi bir yol göremiyorum. Sleep
'u temizlemek için Func<bool>
yerine bir WaitHandle
geçmek istiyorum, ancak ikincisi tekrar temizlemek için tüm Dispose
karışıklık var.
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);
}
}
soru
Özellikle bu çözümlerden birini bakımı, ne de ikisinden% 100 doğru% 100 eminim yoktur. Bu çözümlerden biri diğerinden (idiyomite, verimlilik, vb.) Daha mı iyi, yoksa burada yapmam gerekenleri karşılamak için daha kolay bir yol mu var?
Güncelleme: En iyi yanıt şu ana kadar
aşağıda TaskCompletionSource
çözüm için bir varyasyon. Uzun kapaklar, kilitler veya gerekli herhangi bir şey yok. Oldukça basit görünüyor. Burada herhangi bir hata var mı?
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;
}
Güncelleme 2: Başka harika bir çözüm
BlockingCollection
eserler sadece ConcurrentQueue
gibi çıkıyor ama aynı zamanda bir zaman aşımı ve iptal jetonu kabul yöntemleri vardır. Bu çözüm hakkında güzel bir şey WaitForNEvents
oldukça kolay hale getirmek için güncellenebilir olmasıdır:
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();
}
}
"AutoResetEvent" gibi bir şey istediğiniz gibi geliyor. Kullanabilme ihtimaline baktın mı? –
@KendallFrey Evet, bu beni "ManualResetEventSlim" in yaptığı "Dispose" karmaşasına sokacak gibi görünüyor mu, yoksa etrafta bir yol var mı? – lobsterism
Resharper'ın şikayet etmesinin nedeni, abonelik ve abonelikten çıkma eylemlerinin geçirilmesinden bu yana akışı analiz edememesidir. Olayı (yansımayla) geçirdiyseniz veya bir şekilde akışı daha doğrulanabilir hale getirdiyse şikayeti durdurabilir. Her halükarda, kişisel olarak Resharper uyarılarını yüksek saygı göstermiyorum. –