Как ждать одиночное событие в C # с таймаутом и отменой

Таким образом, мое требование состоит в том, чтобы моя функция ожидала первого экземпляраevent Action исходящий из другого класса и другого потока, и обрабатывать его в моем потоке, позволяя прерывать ожидание по таймауту или.CancellationToken

Я хочу создать универсальную функцию, которую я могу использовать повторно. Мне удалось создать пару вариантов, которые делают (я думаю) то, что мне нужно, но оба кажутся более сложными, чем я.Я думаю, что так и должно быть.

использование

Просто чтобы прояснить, пример использования этой функции будет выглядеть так, гдеserialDevice выплевывает события в отдельный поток:

var eventOccurred = Helper.WaitForSingleEvent(
    cancellationToken,
    statusPacket => OnStatusPacketReceived(statusPacket),
    a => serialDevice.StatusPacketReceived += a,
    a => serialDevice.StatusPacketReceived -= a,
    5000,
    () => serialDevice.RequestStatusPacket());
Опция 1-ManualResetEventSlim

Эта опция неэто плохо, ноDispose обработкаManualResetEventSlim грязнее, чем кажется. Это дает ReSharper припадки, что яДоступ к модифицированным / утилизированным вещам в закрытии, и это 'действительно трудно следовать, поэтому яя даже не уверенправильно. Может бытьчто-то яЯ скучаю по тому, что может очистить это, что было бы моим предпочтением, но я нене вижу это случайно. Вот'с кодом.

public static bool WaitForSingleEvent(this CancellationToken token, Action handler, Action subscribe, Action unsubscribe, int msTimeout, Action initializer = null)
{
    var eventOccurred = false;
    var eventResult = default(TEvent);
    var o = new object();
    var slim = new ManualResetEventSlim();
    Action 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;
    }
}
Вариант 2—опрос безWaitHandle

WaitForSingleEvent Функция здесь намного чище. Я'я могу использоватьConcurrentQueue и, таким образом, недаже не нужен замок. Но я просто нене нравится функция опросаSleepи я неЯ не вижу возможности обойти это с этим подходом. Я'хотел бы пройти вWaitHandle вместоFunc очиститьSleep, но второе, что я делаю, я 'у нас есть всеDispose беспорядок, чтобы навести порядок снова.

public static bool WaitForSingleEvent(this CancellationToken token, Action handler, Action subscribe, Action unsubscribe, int msTimeout, Action initializer = null)
{
    var q = new ConcurrentQueue();
    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 exitCondition)
{
    var start = DateTime.Now;
    while ((DateTime.Now - start).TotalMilliseconds < ms && !exitCondition())
    {
        token.ThrowIfCancellationRequested();
        Thread.Sleep(1);
    }
}
Вопрос

Я неМне не особенно важны эти решения, и я не уверен на 100%, что они верны на 100%. Является ли одно из этих решений лучше, чем другое (идиоматичность, эффективность и т. Д.), Или есть более простой способ или встроенная функция для удовлетворения того, что мне нужно сделать здесь?

Обновление: лучший ответ на данный момент

МодификацияTaskCompletionSource Решение ниже. Никаких длинных закрытий, замков или чего-либо еще. Кажется довольно просто. Здесь есть ошибки?

public static bool WaitForSingleEvent(this CancellationToken token, Action onEvent, Action subscribe, Action unsubscribe, int msTimeout, Action initializer = null)
{
    var tcs = new TaskCompletionSource();
    Action 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;
}
Обновление 2: еще одно отличное решение

Получается чтоBlockingCollection работает так же, какConcurrentQueue но также есть методы, принимающие токен таймаута и отмены. Хорошая особенность этого решения в том, что оно может быть обновлено, чтобы сделатьWaitForNEvents довольно легко:

public static bool WaitForSingleEvent(this CancellationToken token, Action handler, Action subscribe, Action unsubscribe, int msTimeout, Action initializer = null)
{
    var q = new BlockingCollection();
    Action 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();
    }
}

Ответы на вопрос(2)

Ваш ответ на вопрос