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

如何在C#中实现WaitUntilEvent?串口通信场景下异步等待事件触发或超时的实现方案

解决方案:用TaskCompletionSource实现异步等待事件或超时

这个场景用TaskCompletionSource来解决最适合不过了——它能帮你把事件触发转换成可等待的Task,再结合Task.WhenAny就能轻松实现「等待事件触发或超时」的异步逻辑,完全不会阻塞UI线程。下面是针对你业务场景的具体实现:

核心思路

  1. 创建TaskCompletionSource对象,它相当于一个"异步信号器",事件触发时我们用它来标记任务完成。
  2. 临时订阅目标事件,事件触发时调用TrySetResult完成TaskCompletionSource的任务。
  3. 用Task.WhenAny同时等待事件触发任务和超时任务(由Task.Delay创建),哪个先完成就执行对应逻辑。
  4. 最后一定要取消事件订阅,避免内存泄漏。

具体代码实现

1. 实现AskHardwareIfReadyAndWaitForAnswer方法

private async Task AskHardwareIfReadyAndWaitForAnswer()
{
    // 创建TaskCompletionSource,用bool标记是否成功收到响应
    using var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    
    // 定义事件处理逻辑:硬件就绪时完成任务
    void HardwareReadyHandler(object? sender, EventArgs e)
    {
        tcs.TrySetResult(true);
    }

    try
    {
        // 订阅硬件就绪事件
        HardwareSaysItIsReadyEvent += HardwareReadyHandler;
        
        // 发送询问消息
        serialPort.WriteLine("Are you ready?");
        
        // 等待事件触发 或 1秒超时
        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1000));
        
        if (completedTask == tcs.Task)
        {
            // 收到硬件响应,继续后续流程
            Console.WriteLine("硬件已就绪,准备发送问题");
        }
        else
        {
            // 超时处理:抛出异常或执行自定义逻辑
            throw new TimeoutException("等待硬件就绪超时(1秒)");
        }
    }
    finally
    {
        // 必须取消订阅,避免内存泄漏和重复触发
        HardwareSaysItIsReadyEvent -= HardwareReadyHandler;
    }
}

2. 实现AskHardwareIfQuestionWasReceivedAndWaitForAnswer方法

这个方法需要先发送确认消息再完成等待,逻辑稍有不同:

private async Task AskHardwareIfQuestionWasReceivedAndWaitForAnswer()
{
    using var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    
    void PcReadyHandler(object? sender, EventArgs e)
    {
        // 先发送PC就绪的确认消息
        serialPort.WriteLine("yes, go ahead");
        // 完成任务,继续接收答案的逻辑
        tcs.TrySetResult(true);
    }

    try
    {
        HardwareAsksIfPcIsReadyEvent += PcReadyHandler;
        serialPort.WriteLine("Did you get it?");
        
        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1000));
        
        if (completedTask != tcs.Task)
        {
            throw new TimeoutException("等待硬件确认收到问题超时(1秒)");
        }
    }
    finally
    {
        HardwareAsksIfPcIsReadyEvent -= PcReadyHandler;
    }
}

优化:封装通用等待方法

为了避免重复代码,可以把等待逻辑封装成通用方法:

private async Task WaitForEventOrTimeout(EventHandler eventToWaitFor, Action sendMessage, int timeoutMs = 1000)
{
    using var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    
    void EventHandlerWrapper(object? sender, EventArgs e) => tcs.TrySetResult(true);

    try
    {
        eventToWaitFor += EventHandlerWrapper;
        sendMessage();
        
        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(timeoutMs));
        
        if (completedTask != tcs.Task)
        {
            throw new TimeoutException($"等待事件触发超时({timeoutMs}ms)");
        }
    }
    finally
    {
        eventToWaitFor -= EventHandlerWrapper;
    }
}

调用时就会非常简洁:

// 等待硬件就绪
await WaitForEventOrTimeout(HardwareSaysItIsReadyEvent, () => serialPort.WriteLine("Are you ready?"));
// 等待硬件确认收到问题
await WaitForEventOrTimeout(HardwareAsksIfPcIsReadyEvent, () => serialPort.WriteLine("Did you get it?"));

关键注意事项

  • 必须取消事件订阅:如果不取消,事件处理器会一直绑定在事件上,导致内存泄漏,甚至后续触发多次回调。
  • 使用RunContinuationsAsynchronously:确保任务完成时的回调不会阻塞SerialPort的DataReceived线程(该线程是后台线程,阻塞可能导致通信异常)。
  • 异常处理:可以根据业务需求调整超时逻辑,比如不抛出异常而是返回bool值标记是否超时。

内容的提问来源于stack exchange,提问作者Ahmad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 15:27:40