强制循环等待事件

Force loop to wait for an event

我正在尝试编写一种遍历项目列表的方法 - 将每个项目添加到表单中,然后等待用户输入一些数据并单击按钮。但是我不知道我 should/could 是用什么来创建这个的。

foreach (string s in List)
{
    txtName.Text = s;
    //wait for button click...

    // When the user is ready, they click the button to continue the loop.
}

到目前为止我只找到了EventWaitHandle class,它似乎只适用于线程。

我怎样才能做到这一点?

如果您还使用 async/await, 种使用信令执行此操作的方法 - 例如,您可以等待通过单击按钮完成的任务- 但总的来说,您应该以更加事件驱动的方式来考虑用户界面。

不要让您的 foreach 在此处循环,而是在要显示的项目的集合中保留一个索引,并在每次单击按钮时推进它。 (当然,请记住检查是否有 个要显示的项目。)

虽然我大体同意 Jon Skeet,但 Mads Torgensen 做了一个有趣的演讲,展示了 async/await 如何能够用于简化此类场景(使用 Jon 提到的技术)。毕竟,这与枚举器不一样——我们可以使用索引等状态编写自己的枚举器 class,但我们几乎从不这样做,而是使用迭代器块。

无论如何,这就是我们正在谈论的 async/await 技术。

首先,可重复使用的部分:

public static class Utils
{
    public static Task WhenClicked(this Button button)
    {
        var tcs = new TaskCompletionSource<object>();
        EventHandler onClick = null;
        onClick = (sender, e) =>
        {
            button.Click -= onClick;
            tcs.TrySetResult(null);
        };
        button.Click += onClick;
        return tcs.Task;
    }
}

以及您使用它的代码(请注意,您需要将您的方法标记为 async

foreach (string s in List)
{
    txtName.Text = s;
    await yourButton.WhenClicked();
}

将所有内容放在一起的示例测试:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Samples
{
    static class Test
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var form = new Form();
            var txtName = new TextBox { Parent = form, Top = 8, Left = 8 };
            var buttonNext = new Button { Parent = form, Top = txtName.Bottom + 8, Left = 8, Text = "Next" };
            form.Load += async (sender, e) =>
            {
                var List = new List<string> { "A", "B", "C", "D " };
                foreach (string s in List)
                {
                    txtName.Text = s;
                    await buttonNext.WhenClicked();
                }
                txtName.Text = "";
                buttonNext.Enabled = false;
            };
            Application.Run(form);
        }
    }
    public static class Utils
    {
        public static Task WhenClicked(this Button button)
        {
            var tcs = new TaskCompletionSource<object>();
            EventHandler onClick = null;
            onClick = (sender, e) =>
            {
                button.Click -= onClick;
                tcs.TrySetResult(null);
            };
            button.Click += onClick;
            return tcs.Task;
        }
    }
}

我和 Jon Skeet 在这方面是一致的,试图强制 UI 框架进入一种不正常的工作方式,因为它通常会导致问题。即使它能工作,你的代码也很难被其他人理解,因为它会以一种不寻常的方式工作。

Lisp 中的某些 Web 框架的工作方式使页面网页显示看起来像方法调用。我还没有在 .NET 中看到任何这样做的东西。

我的第一个想法是让你在一个循环中调用 Application.DoEvents() 检查你的按钮回调设置的标志。但是,您的应用程序将在无所事事时使用 100% 的 CPU 等待。参见 http://blog.codinghorror.com/is-doevents-evil/

您的应用程序是一个 finite state machine,它响应来自用户的事件,并在所需事件到达以匹配当前状态的状态转换时进入新状态。多年来,已经有很多尝试在代码中对此进行建模,none 我看到已经提出了比仅使用标准事件处理更好的方法。