.NET Framework 4.8 WinForms启动任务后等待事件的异步实现问题
问题分析与解决方案
你的核心问题集中在UI线程阻塞和事件等待逻辑失效两点上,下面是针对性的修改方案和原因说明:
1. 修复UI线程阻塞:修改按钮点击事件
WinForms的UI事件允许使用async void(仅在UI事件场景下推荐),避免用GetAwaiter().GetResult()强制同步等待导致的冻结:
private async void btnLaunch_Click(object sender, EventArgs e) { // 禁用按钮防止重复触发 btnLaunch.Enabled = false; try { await CallAsync(); // 后续可以添加完成后的UI提示逻辑 } finally { btnLaunch.Enabled = true; } }
2. 简化异步流程:重构CallAsync方法
去掉冗余的Task.Run和ContinueWith,用直白的顺序await实现分步等待,同时避免OCX跨线程访问风险(旧OCX通常依赖STA线程):
private async Task<bool> CallAsync() { // 启动硬件进程 await ruleEngine.ExecuteStep(new RuleEngine.Clases.Step() { MethodName = "Start", Parameters = null }); // 等待硬件完成触发End事件 await ruleEngine.ExecuteStep(new RuleEngine.Clases.Step() { MethodName = "End", Parameters = null }); // 这里添加后续步骤,比如MoveAxis // await ruleEngine.ExecuteStep(new RuleEngine.Clases.Step() { MethodName = "MoveAxis", Parameters = new object[] { 75, 10 } }); return true; }
3. 修复事件等待失效:重构RuleEngine的End事件处理
原代码中全局TaskCompletionSource<T>只能触发一次,后续等待会直接返回,必须为每次事件等待创建独立的TCS:
public class RuleEngine { private readonly List<object> _objects; public RuleEngine(List<object> objects) { _objects = objects; } public async Task<object> ExecuteStep(Step step) { foreach (var obj in _objects) { MethodInfo method = obj.GetType().GetMethod(step.MethodName); if (method != null) { if (method.ReturnType == typeof(Task)) { await (Task)method.Invoke(obj, step.Parameters?.ToArray()); return null; } else if (typeof(Task).IsAssignableFrom(method.ReturnType)) { return await ((dynamic)method.Invoke(obj, step.Parameters?.ToArray())); } else if (method.Name == "Start") { // 直接调用Start方法(确保该方法仅启动进程而非阻塞) return method.Invoke(obj, step.Parameters?.ToArray()); } } else if (step.MethodName == "End") { var ocx = (AccessOCX)obj; // 为本次等待创建独立的TCS var eventTcs = new TaskCompletionSource<object>(); // 定义临时事件处理程序 void EndEventHandler(double executionTime, ResultConstants result) { ocx.End -= EndEventHandler; // 先解绑避免重复触发 eventTcs.TrySetResult(null); } ocx.End += EndEventHandler; try { await eventTcs.Task; return true; } finally { // 确保异常场景下也解绑事件,避免内存泄漏 ocx.End -= EndEventHandler; } } } Console.WriteLine($"Method {step.MethodName} not found"); return null; } }
关键修改说明
- 全局TCS替换为局部TCS:原全局TCS在第一次触发后就处于完成状态,后续等待会直接跳过,无法响应新的End事件。每次等待创建独立TCS才能保证事件等待的正确性。
- 移除Task.Run:旧OCX通常依赖STA线程(WinForms UI线程默认是STA),跨线程操作OCX可能导致事件不触发或异常,直接在UI线程上下文执行异步等待即可。
- 事件处理程序及时解绑:避免OCX多次触发End事件导致逻辑混乱,同时防止内存泄漏。
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

