【问题标题】:How to block until an event is fired in c#如何阻止直到在c#中触发事件
【发布时间】:2012-10-05 12:01:27
【问题描述】:

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

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 返回给调用它的任何对象。

The accepted answerthat question 我问的是我做了什么,但感觉不太对劲。几乎感觉在 ManuelResetEvent 触发和程序检索数据并返回数据之间的数据可能会发生一些事情。

更新:Auto/ManualResetEvent 的主要问题是它太容易受到攻击。一个线程可以等待该事件,然后在将其更改为其他内容之前没有给其他任何人足够的时间来获取它。有没有办法使用锁或其他东西?也许使用 get 和 set 语句。

【问题讨论】:

  • while 循环怎么样:while (someGlobalvar); cahnge someGlobalvar 在您分配给事件的函数中。
  • 它是主动等待,这是一个非常糟糕的解决方案
  • 除了这个问题早于那个问题:)
  • @Vahid,自从我提出这个问题以来,我已有 7 年的编程经验,我想说,如果你必须这样做,那么你可能做错了。最好指定一个回调。如果您正在使用不允许其他任何东西的旧系统,例如 COM,那么只需让代码等待 ManualResetEvent 并设置第二个以保护写入,直到所有线程都完成读取,我猜。
  • 我还建议您查看reactivex.io

标签: c# events


【解决方案1】:

如果当前方法是异步的,那么您可以使用 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";

【讨论】:

  • 你不需要异步来使用TaskCompletionSource
  • 确实如此,但如果您想在不阻塞 UI 的情况下等待任务,则确实需要它。
  • 可以重置TaskCompletionSource吗?
  • @KyleDelaney 否。在我的示例中,您将用新实例替换 tcs
  • 如何使用不是按钮单击的事件来实现这一点?有没有办法调用一个事件等待处理程序完成执行,然后在最后调用 TrySetResult(true)?
【解决方案2】:

您可以使用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
}

【讨论】:

  • 这不会直接返回值。我已经在另一个问题中尝试过。没有我想问的吗?
【解决方案3】:

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

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

你解雇他们打电话给Set()

Here 是文档。

【讨论】:

  • 但这不会返回任何类型的值。
  • 你能编辑 qu 并添加不同组件及其交互的概述吗?这将帮助我们更好地了解您想要实现的目标,并有望带来改进的设计。
【解决方案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 消息以避免锁定,但这段代码显示它按预期工作。

【讨论】:

    猜你喜欢
    • 2010-11-25
    • 2014-01-24
    • 2021-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-28
    相关资源
    最近更新 更多