Подтвердить что ты не робот

Как заблокировать, пока событие не будет запущено в С#

После запроса этого вопроса, мне интересно, можно ли ожидать, когда будет запущено событие, а затем получить данные о событии и вернуть его часть. Пример:

private event MyEventHandler event;
public string ReadLine(){ return event.waitForValue().Message; }
...
event("My String");
...elsewhere...
var resp = ReadLine();

Пожалуйста, убедитесь, что любое решение, которое вы предоставляете, возвращает значение напрямую, а не получает его от чего-то другого. Я спрашиваю, доступен ли какой-либо метод выше. Я знаю об Auto/ManuelResetEvent, но я не знаю, что они возвращают значение напрямую, как я сделал выше.

Обновление: Я объявил событие с использованием MyEventHandler (который содержит поле Message). У меня есть метод в другом потоке с именем ReadLine, ожидающем, когда событие будет запущено. Когда событие срабатывает, метод WaitForValue (часть сцены обработки событий) возвращает аргументы событий, которые содержат сообщение. Сообщение затем возвращается ReadLine ко всему, что вызвало его.

Принятый ответ на этот вопрос Я спросил, что я сделал, но это просто не чувствую себя совершенно правильно. Похоже, что что-то может произойти с данными между запуском ManuelResetEvent и программой, получающей данные и возвращающими ее.

Обновление: Основная проблема с Auto/ManualResetEvent заключается в том, что она слишком уязвима. Поток мог дождаться события, а затем не дать достаточно времени, чтобы кто-либо еще мог его получить, прежде чем менять его на что-то другое. Есть ли способ использовать блокировки или что-то еще? Может быть, использовать инструкции get и set.

4b9b3361

Ответ 1

Вы можете использовать ManualResetEvent. Reset событие перед тем, как вы активируете вторичный поток, а затем используйте метод WaitOne() для блокировки текущего потока. Затем вы можете установить дополнительный поток ManualResetEvent, который приведет к продолжению основного потока. Что-то вроде этого:

ManualResetEvent oSignalEvent = new ManualResetEvent(false);

void SecondThread(){
    //DoStuff
    oSignalEvent.Set();
}

void Main(){
    //DoStuff
    //Call second thread
    System.Threading.Thread oSecondThread = new System.Threading.Thread(SecondThread);
    oSecondThread.Start();

    oSignalEvent.WaitOne(); //This thread will block here until the reset event is sent.
    oSignalEvent.Reset();
    //Do more stuff
}

Ответ 2

Если текущий метод является асинхронным, вы можете использовать TaskCompletionSource. Создайте поле, к которому может обращаться обработчик событий и текущий метод.

    TaskCompletionSource<bool> tsc = null;

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        tsc = new TaskCompletionSource<bool>();
        await tsc.Task;
        WelcomeTitle.Text = "Finished work";
    }

    private void Button_Click2(object sender, RoutedEventArgs e)
    {
        tsc?.TrySetResult(true);
    }

В этом примере используется форма с текстовым блоком с именем WelcomeTitle и двумя кнопками. Когда нажата первая кнопка, она запускает событие щелчка, но останавливается на линии ожидания. Когда нажимается вторая кнопка, задача завершается, и текст WelcomeTitle обновляется. Если вы хотите также использовать тайм-аут, измените

await tsc.Task;

к

await Task.WhenAny(tsc.Task, Task.Delay(25000));
if (tsc.Task.IsCompleted)
    WelcomeTitle.Text = "Task Completed";
else
    WelcomeTitle.Text = "Task Timed Out";

Ответ 3

Очень простое событие, которое вы можете подождать, это ManualResetEvent, и даже лучше, ManualResetEventSlim.

У них есть метод WaitOne(), который делает именно это. Вы можете подождать вечно или установить тайм-аут или "маркер отмены", который позволяет вам прекратить ждать события (если вы хотите отменить свою работу или ваше приложение попросило выйти).

Вы вызываете их при вызове Set().

Здесь находится документ.

Ответ 4

Если вы счастливы использовать Microsoft Reactive Extensions, то это может работать красиво:

public class Foo
{
    public delegate void MyEventHandler(object source, MessageEventArgs args);
    public event MyEventHandler _event;
    public string ReadLine()
    {
        return Observable
            .FromEventPattern<MyEventHandler, MessageEventArgs>(
                h => this._event += h,
                h => this._event -= h)
            .Select(ep => ep.EventArgs.Message)
            .First();
    }
    public void SendLine(string message)
    {
        _event(this, new MessageEventArgs() { Message = message });
    }
}

public class MessageEventArgs : EventArgs
{
    public string Message;
}

Я могу использовать его так:

var foo = new Foo();

ThreadPoolScheduler.Instance
    .Schedule(
        TimeSpan.FromSeconds(5.0),
        () => foo.SendLine("Bar!"));

var resp = foo.ReadLine();

Console.WriteLine(resp);

Мне нужно было вызвать сообщение SendLine в другом потоке, чтобы избежать блокировки, но этот код показывает, что он работает как ожидалось.