70

问完这个问题后,我想知道是否可以等待一个事件被触发,然后获取事件数据并返回其中的一部分。有点像这样:

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 语句。

4

4 回答 4

53

如果当前方法是异步的,那么您可以使用 TaskCompletionSource。创建事件处理程序和当前方法可以访问的字段。

    TaskCompletionSource<bool> tcs = null;

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

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

此示例使用具有名为 WelcomeTitle 的文本块和两个按钮的表单。单击第一个按钮时,它会启动 click 事件,但会在 await 行处停止。单击第二个按钮时,任务完成并更新 WelcomeTitle 文本。如果您也想超时,请更改

await tcs.Task;

await Task.WhenAny(tcs.Task, Task.Delay(25000));
if (tcs.Task.IsCompleted)
    WelcomeTitle.Text = "Task Completed";
else
    WelcomeTitle.Text = "Task Timed Out";
于 2016-07-06T14:00:35.903 回答
45

您可以使用 ManualResetEvent。在触发辅助线程之前重置事件,然后使用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
}
于 2012-10-05T12:10:51.963 回答
3

您可以等待的一种非常简单的事件是ManualResetEvent,甚至更好的是ManualResetEventSlim.

他们有一种WaitOne()方法可以做到这一点。你可以永远等待,或者设置一个超时,或者一个“取消令牌”,这是你决定停止等待事件的一种方式(如果你想取消你的工作,或者你的应用程序被要求退出)。

你解雇他们打电话Set()

是文档。

于 2012-10-05T12:12:10.107 回答
1

如果您乐于使用 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在不同的线程上调用消息以避免锁定,但这段代码显示它按预期工作。

于 2012-10-05T12:36:04.110 回答