【问题标题】:Block code until event fires (Like modal dialog)阻止代码直到事件触发(如模式对话框)
【发布时间】:2014-01-24 07:30:17
【问题描述】:

模式对话框会阻塞代码,直到它返回。我想要没有新窗口的同样的东西。

一个带有单独对话框的简单示例(不是我要找的)

MessageBox.Show("Continue");
MessageBox.Show("This waits until the first returns.");

我正在寻找的一个简单的不工作示例:

mres1.Wait();//Where: ManualResetEventSlim mres1 = new ManualResetEventSlim(false); 
MessageBox.Show("This should wait until button2 is clicked.");

+

private void button2_Click(object sender, EventArgs e)
{
    mres1.Set();
}

是的,我知道这会失败,因为它阻塞了整个线程。但这就是我正在寻找的那种系统,只有不阻塞线程,只有代码。这可能吗?

(我不是在寻找解决方法(-“在事件的事件处理程序中调用其余代码。”)。我在问是否可以按照描述的方式完成。)

编辑:目标是用PanelDock = DockStyle.Fill)替换我目前拥有的模式对话框。

【问题讨论】:

  • 您使用的是 Visual Studio 2012 还是更新版本?
  • @ScottChamberlain VS2010.
  • 在主线程上恢复执行的催化剂是什么?
  • @ispiro 如果您使用 Visual Studio 2012,您可以通过 Microsoft.Bcl.Async 在 .NET 4.0 中使用 async/await,这将是完美的选择。但你不是,所以我将此作为评论而不是答案。
  • @ispiro 不,await 只是在等待 Task 完成而不阻塞 UI 线程。 Tasks 是显示某种“异步工作”的包装器。这项工作可以是running a thread,等待for a event,或者作为旧APM model 的包装器(使用BeginXxxx(EndXxxx()。任务(和等待)并不意味着线程。

标签: c# .net winforms visual-studio-2010 .net-4.0


【解决方案1】:

这是SiLo's old solution 的略微修改版本,不使用任何额外的线程,仍然适用于 .NET 4.0

编辑:更新为使用面板,可能需​​要进行更多调整,但我认为这将使您完成 99% 的工作。

private TaskCompletionSource<object> waiter = new TaskCompletionSource<object>(); 

private void button1_Click(object sender, EventArgs e)
{

    Panel coverScreen = new Panel();
    coverScreen.BackColor = Color.Black;
    coverScreen.Dock = DockStyle.Fill;
    this.Controls.Add(coverScreen);

    waiter.Task.ContinueWith(_ =>
    {
        this.Controls.Remove(coverScreen);
        coverScreen.Dispose();
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

private void button2_Click(object sender, EventArgs e)
{
    waiter.SetResult(null);
    waiter = new TaskCompletionSource<object>(); //Reset the TaskCompletionSource
}

【讨论】:

  • @ispiro 查看使用面板的更新版本。我想我明白你的目的,这让你快到了。
  • 我喜欢这个解决方案!甚至没有考虑手动设置Task.Result 来触发继续。整洁!
【解决方案2】:

编辑:我想我更了解你的意图。基本上,您希望创建自己的模态行为,而不是使用系统默认值。

在这种情况下,您希望两个单独的代码块一个接一个地运行,并等待一些用户交互来触发转换(例如OK 按钮或Hide() 方法)。

也许最好制作自己的ModalPanel UI 元素,该元素将从基础Panel 控件驱动。在您的子类中,您可以公开一个 ShowModal 方法,让您放入一个任务,并且一旦隐藏了 ModalPanel,它就会执行。

例如:

class ModalPanel : Panel
{
    protected readonly Button okButton;
    Task task;

    public ModalPanel()
    {
        okButton = new Button();

        okButton.Width = 100;
        okButton.Height = 32;

        okButton.Left = (this.Width - okButton.Width) / 2;
        okButton.Top = (this.Height - okButton.Height) / 2;

        okButton.Text = "OK";
        okButton.Click += delegate { this.Hide(); };
    }

    public virtual void ShowModal(Task completion)
    {
        this.task = completion;

        this.Dock = DockStyle.Fill;
        this.Show();
    }

    protected override void OnVisibleChanged(EventArgs e)
    {
        base.OnVisibleChanged(e);

        var isHidden = !this.Visible;

        if (isHidden && task != null)
            task.Start();
    }
}

【讨论】:

  • SynchronizationContext 呢?
  • 这取决于 OP 是否需要 UI 上下文(如果他计划继续使用它)。将更新我的答案以包括这种可能性。
  • @L.B(和 SiLo)添加了一个编辑(在我的问题末尾)以澄清。
  • 当一个按钮被点击时我应该如何让它继续?
  • 感谢您的编辑。但这就像提取延续代码并从面板的关闭按钮调用它(尽管当然编码更好)。
【解决方案3】:

如果您想继续更新 UI,那么您应该研究 await/async 方法,但是您可以通过这种方式在非 UI 线程上等待/发送信号:

如果你偏爱 TPL,你可以使用 Task

ManualResetEventSlim mres1 = null;

private void button1_Click(object sender, EventArgs e)
{
   System.Threading.ThreadPool.QueueUserWorkItem( Wait , null );
}

private void Wait(object state)
{
    mres1 = new ManualResetEventSlim(false);
    mres1.Wait();
    //rest of the code to execute....
}
private void button2_Click(object sender, EventArgs e)
{
    mres1.Set();

}

【讨论】:

  • 不是我。我还在尝试。 (它在 ThreadPool 处给了我一个错误“无效参数”...)
  • 好吧,让我去签名吧……这行得通,我一直在做这种事情。
  • @L.B 如果他试图修改 UI 然后同意,他将不得不在 UI 线程上调用。没有我在哪里读到他想更新用户界面。如果他想要 UI,他可能应该使用异步
  • 添加了一个编辑(在我的问题末尾)以澄清。
  • 我的解决方案适合您,只需将其合并到您的新对话窗口中即可。然而,就像其他海报提到的那样,要让延迟的代码更新 UI,您需要通过 UI 线程“调用”它。
【解决方案4】:

await 结合将您想要的任何内容转换为任务的方法,使这变得简单、清晰和有效。

private async void button1_Click(object sender, EventArgs e)
{
    var tcs = new TaskCompletionSource<bool>();
    button2.Click += (s, args) => tcs.TrySetResult(true);
    await tcs.Task;
    DoSomething();
}

【讨论】:

  • 添加了一个编辑(在我的问题末尾)以澄清。
  • @ispiro 好的。使用这种模式很容易做到。
  • 这应该适用于 VS2010/.net4.0 吗? VS 甚至无法识别async
  • @ispiro 不,这是 .NET 4.5 的东西。
  • 它可以在 VS2012 / .NET 4.0 上使用 Microsoft.Bcl.Async。不过,它确实需要 VS2012/VS2013。
猜你喜欢
  • 2012-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多