Vì vậy, yêu cầu của tôi là để chức năng của tôi chờ phiên bản event Action<T>
đến từ một lớp khác và một chủ đề khác, và xử lý nó trên chủ đề của tôi bị gián đoạn bởi thời gian chờ hoặc CancellationToken
.Làm thế nào để chờ một sự kiện trong C#, với thời gian chờ và hủy
Tôi muốn tạo một hàm chung mà tôi có thể sử dụng lại. Tôi đã xoay xở để tạo ra một vài lựa chọn làm (tôi nghĩ) những gì tôi cần, nhưng cả hai dường như phức tạp hơn tôi tưởng tượng nó phải có.
Cách sử dụng
Chỉ cần được rõ ràng, việc sử dụng mẫu của chức năng này sẽ trông như thế này, nơi serialDevice
là phun ra các sự kiện trên một sợi riêng biệt:
var eventOccurred = Helper.WaitForSingleEvent<StatusPacket>(
cancellationToken,
statusPacket => OnStatusPacketReceived(statusPacket),
a => serialDevice.StatusPacketReceived += a,
a => serialDevice.StatusPacketReceived -= a,
5000,
() => serialDevice.RequestStatusPacket());
Lựa chọn 1-ManualResetEventSlim
Tùy chọn này không phải là xấu, nhưng việc xử lý Dispose
của ManualResetEventSlim
là lộn xộn hơn nó có vẻ như nó phải được. Nó cho ReSharper phù hợp với việc tôi đang truy cập vào những thứ đã được sửa đổi/xử lý trong phạm vi đóng cửa, và thật khó để làm theo vì vậy tôi thậm chí không chắc nó là chính xác. Có lẽ có điều gì đó mà tôi bỏ lỡ có thể làm sạch nó, đó sẽ là sở thích của tôi, nhưng tôi không thấy nó một cách dễ dàng. Đây là mã.
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;
}
}
Lựa chọn 2-bỏ phiếu mà không có một WaitHandle
Các WaitForSingleEvent
chức năng ở đây là sạch hơn nhiều. Tôi có thể sử dụng ConcurrentQueue
và do đó thậm chí không cần khóa. Nhưng tôi không thích chức năng bỏ phiếu Sleep
và tôi không thấy bất kỳ cách nào xung quanh nó với phương pháp này. Tôi muốn chuyển một số WaitHandle
thay vì Func<bool>
để làm sạch Sleep
, nhưng lần thứ hai tôi làm điều đó tôi đã có toàn bộ mess Dispose
để dọn dẹp lại.
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);
}
}
Câu hỏi
Tôi không đặc biệt quan tâm đối với một trong các giải pháp này, tôi cũng không chắc chắn 100% một trong hai trong số đó là 100% chính xác. Hoặc là một trong những giải pháp này tốt hơn các giải pháp khác (tính tự nhiên, hiệu quả, v.v.), hoặc có cách nào dễ hơn hoặc chức năng tích hợp để đáp ứng những gì tôi cần làm ở đây không?
Cập nhật: Câu trả lời hay nhất
Sửa đổi giải pháp TaskCompletionSource
bên dưới. Không có đóng cửa dài, ổ khóa, hoặc bất cứ điều gì cần thiết. Có vẻ khá đơn giản. Có lỗi nào ở đây không?
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;
}
Cập nhật 2: Một giải pháp tuyệt vời
Hóa ra rằng BlockingCollection
công trình giống như ConcurrentQueue
mà còn có phương pháp chấp nhận một thời gian chờ và hủy thẻ. Một điều thú vị về giải pháp này là nó có thể được cập nhật để thực hiện một WaitForNEvents
khá dễ dàng:
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();
}
}
Có vẻ như bạn muốn một cái gì đó như 'AutoResetEvent'. Bạn đã xem xét khả năng sử dụng nó chưa? –
@KendallFrey Vâng, điều đó dường như khiến tôi rơi vào cùng một mớ hỗn độn 'Dispose' mà' ManualResetEventSlim' làm, hay bạn có cách nào đó xung quanh nó? – lobsterism
Lý do Resharper than phiền là vì nó không thể phân tích luồng do hành động đăng ký và hủy đăng ký được chuyển vào. Nó có thể ngừng phàn nàn nếu bạn vượt qua sự kiện (thông qua phản ánh) hoặc bằng cách nào đó làm cho luồng được xác minh hơn. Trong mọi trường hợp, cá nhân tôi không giữ cảnh báo Resharper trong vấn đề cao. –