如何在异步方法中等待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,提问作者金志坚
相关产品推荐
相关产品推荐

