You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在异步方法中等待Button点击事件?

问题描述

我需要编写读取JSON文件的代码,让用户逐个输入JSON中每个对象的所有参数。尝试实现“可等待的Button”功能,但无法为System.Windows.Forms.Button编写GetAwaiter扩展方法。

点击loadJSON按钮后的核心逻辑代码:

for (int i = 0; i<templist_net.Count; i++)
{
    GeneratorFunctions.GetNetworkParameterList(templist_net[i].Type, templist_net[i], treeViewPath.SelectedPath, SolutionFolder);
    cBoxPouItem.Text = templist_net[i].Type;

    ListViewParam2.ItemsSource = GeneratorFunctions.TempList;   // 参数列表绑定
    temp = GeneratorFunctions.TempList;
    ListViewParam2.Visibility = Visibility.Visible;             // 显示参数列表2
    ListViewParam.Visibility = Visibility.Collapsed;            // 隐藏参数列表1

    // 需要暂停循环,等待用户在ListView中完成参数输入并点击按钮后,再继续执行下一次循环
}

我尝试通过自定义控件类编写扩展方法的代码:

public partial class CustomControl2 : System.Windows.Forms.Button
{
    static CustomControl2()
    {

    }
    public static TaskAwaiter GetAwaiter(this Button self)
    {
        ArgumentNullException.ThrowIfNull(self);
        TaskCompletionSource tcs = new();
        self.Click += OnClick;
        return tcs.Task.GetAwaiter();

        void OnClick(object sender, EventArgs args)
        {
            self.Click -= OnClick;

            tcs.SetResult();
        }
    }
}

但这种写法无法实现继承自System.Windows.Forms.Button的扩展方法,该如何解决?

后续尝试使用信号量实现等待的代码:

private async Task Btn_loadJsonAsync(object sender, RoutedEventArgs e) {
    // 初始化打开文件对话框,仅显示.json格式文件
    OpenFileDialog openFileDialog = new OpenFileDialog();
    openFileDialog.Filter = "(*.json)|*.json";
    TextBoxInformation.Text += "Opening project ...\n";
    if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    { 
        networks = GeneratorFunctions.ReadjsonNetwork(openFileDialog.FileName);
        for (int i = 0; i < networks.Count; i++)
        {
            if (temp != null)
            {
                if (networks[i].Type == "Network")
                {
                    templist_net.Add(networks[i]);
                    i = 1;
                }
                if (networks[i].Type == "Subsystem")
                {
                    templist_sub.Add(networks[i]);
                    i = 1;
                }
                if (networks[i].Type == "Component: Data Point Based Control")
                {
                    templist_com.Add(networks[i]);
                    i = 1;
                }
            }
        }
        using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
        {
            void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
            btn.Click += OnClick;

            for (int i = 0; i < templist_net.Count; i++)
            {
                // 参数绑定逻辑...

                // 等待按钮被点击后继续循环
                await semaphore.WaitAsync();
            }

            btn.Click -= OnClick;
        }
    }
}
解决方案

1. 正确编写Button的GetAwaiter扩展方法

你之前的问题在于把扩展方法写在了自定义控件类内部,扩展方法必须放在独立的静态类中,而非自定义控件类里。正确写法如下:

public static class ButtonAwaitExtensions
{
    public static TaskAwaiter GetAwaiter(this System.Windows.Forms.Button button)
    {
        ArgumentNullException.ThrowIfNull(button);
        var tcs = new TaskCompletionSource();
        
        void ClickHandler(object sender, EventArgs e)
        {
            button.Click -= ClickHandler;
            tcs.SetResult();
        }
        
        button.Click += ClickHandler;
        return tcs.Task.GetAwaiter();
    }
}

编写完成后,就能直接在任意System.Windows.Forms.Button实例上使用await,修改你的循环逻辑:

// 确保方法标记为async
private async void YourLoadJsonButton_Click(object sender, EventArgs e)
{
    for (int i = 0; i < templist_net.Count; i++)
    {
        // 原有参数绑定、界面显示逻辑...
        
        // 等待按钮点击后进入下一次循环
        await yourConfirmButton;
    }
}

2. 优化信号量实现的细节

你用SemaphoreSlim的思路可行,但需要修正几个问题:

  • 分类逻辑中i=1的写法会跳过后续元素,改用foreach更安全:
foreach (var item in networks)
{
    if (temp == null) continue;
    
    if (item.Type == "Network")
        templist_net.Add(item);
    else if (item.Type == "Subsystem")
        templist_sub.Add(item);
    else if (item.Type == "Component: Data Point Based Control")
        templist_com.Add(item);
}
  • 增加异常处理,避免用户取消操作时出现资源泄漏:
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
    void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
    btn.Click += OnClick;

    try
    {
        for (int i = 0; i < templist_net.Count; i++)
        {
            // 参数绑定逻辑...
            await semaphore.WaitAsync();
        }
    }
    finally
    {
        btn.Click -= OnClick;
    }
}

3. 关键注意事项

  • 扩展方法所在的静态类必须是顶级类,不能嵌套在其他类中
  • 使用await的方法必须标记为async,且返回值建议用Task而非void(事件处理方法除外)
  • 每次等待后要移除按钮的点击事件绑定,避免重复绑定导致多次触发

内容的提问来源于stack exchange,提问作者金志坚

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 08:55:18