在使用 CancellationToken cancellationToken 方法时,CancellationToken 是一个用于支持任务取消的机制,通常由调用方创建并传递

在使用 public async Task StartRealtimePollingAsync(TimeSpan interval, CancellationToken cancellationToken) 方法时,CancellationToken 是一个用于支持任务取消的机制,通常由调用方创建并传递。以下是关于如何正确创建、传递和使用 cancellationToken 的详细说明,结合你的实时轮询场景,确保代码清晰、可靠且易于维护。


什么是 CancellationToken?

  • CancellationToken 是 .NET 提供的一种机制,用于在异步操作(如轮询硬件数据)中通知任务取消。

  • 它通常与 CancellationTokenSource 一起使用,后者负责创建和控制取消信号。

  • 在你的场景中,StartRealtimePollingAsync 使用 CancellationToken 允许外部调用方在需要时(例如系统关闭、用户操作或错误状态)取消轮询任务。


如何传递 CancellationToken?

以下是创建和传递 CancellationToken 的步骤,以及在你的代码中的具体实现方式。

1. 创建 CancellationTokenSource

  • 调用方需要创建一个 CancellationTokenSource 实例,用于生成 CancellationToken 并控制取消。

  • 示例:

    csharp

    var cts = new CancellationTokenSource();
    CancellationToken token = cts.Token;
  • 可选设置超时:

    • 如果希望轮询在特定时间后自动停止,可以设置超时。

    • 示例:

      csharp

      var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // 30秒后自动取消

2. 传递 CancellationToken

  • 将 cts.Token 作为参数传递给 StartRealtimePollingAsync。

  • 示例:

    csharp

    public async Task StartPollingExampleAsync()
    {
        using var cts = new CancellationTokenSource();
        var interval = TimeSpan.FromMilliseconds(100); // 每100ms轮询一次
        try
        {
            await testChannel.StartRealtimePollingAsync(interval, cts.Token);
        }
        catch (OperationCanceledException)
        {
            Logger.Info("轮询任务已取消");
        }
    }
  • 注意:

    • 使用 using 确保 CancellationTokenSource 在不再需要时被正确释放。

    • OperationCanceledException 是取消时抛出的异常,需捕获以优雅处理。

3. 触发取消

  • 调用方可以通过调用 cts.Cancel() 触发取消,通知 StartRealtimePollingAsync 停止轮询。

  • 示例:

    csharp

    public async Task StartAndCancelPollingAsync()
    {
        using var cts = new CancellationTokenSource();
        var interval = TimeSpan.FromMilliseconds(100);
    
        // 启动轮询
        var pollingTask = testChannel.StartRealtimePollingAsync(interval, cts.Token);
    
        // 模拟5秒后取消
        await Task.Delay(5000);
        cts.Cancel();
    
        try
        {
            await pollingTask;
        }
        catch (OperationCanceledException)
        {
            Logger.Info("轮询任务已取消");
        }
    }
  • 场景:

    • 系统关闭时(如调用 SystemMgr.CloseSystemAsync)。

    • 用户手动停止测试(如通过 UI 按钮)。

    • 硬件错误触发停止轮询。

4. 在 StartRealtimePollingAsync 中使用 CancellationToken

  • 方法内部需要检查 cancellationToken.IsCancellationRequested 或将 CancellationToken 传递给依赖的异步操作(如 Task.Delay、QueryDataAsync)。

  • 你的原始实现:

    csharp

    public async Task StartRealtimePollingAsync(TimeSpan interval, CancellationToken cancellationToken)
    {
        using var timer = new PeriodicTimer(interval);
        while (await timer.WaitForNextTickAsync(cancellationToken))
        {
            try
            {
                var (daqResult, adapterBoardResult) = await QueryHardwareDataAsync(SectionId, cancellationToken);
                _dataCache[SectionId] = (daqResult, adapterBoardResult);
                await ProcessBusinessLogicAsync(daqResult, adapterBoardResult, cancellationToken);
            }
            catch (IOException ex)
            {
                Logger.Error($"硬件查询失败: SectionId={SectionId}", ex);
                await NotifyErrorAsync("硬件通信错误", ex, cancellationToken);
            }
            catch (Exception ex)
            {
                Logger.Error($"未知错误: SectionId={SectionId}", ex);
                await NotifyErrorAsync("未知硬件错误", ex, cancellationToken);
            }
        }
    }
  • 优化点:

    • 确保所有异步调用(如 QueryHardwareDataAsync、ProcessBusinessLogicAsync)都传递 cancellationToken。

    • 在捕获异常后,检查是否因取消抛出异常,以避免重复记录。

    • 示例优化:

      csharp

      public async Task StartRealtimePollingAsync(TimeSpan interval, CancellationToken cancellationToken)
      {
          using var timer = new PeriodicTimer(interval);
          while (await timer.WaitForNextTickAsync(cancellationToken))
          {
              try
              {
                  var (daqResult, adapterBoardResult) = await QueryHardwareDataAsync(SectionId, cancellationToken);
                  if (daqResult != null && adapterBoardResult != null)
                  {
                      _dataCache[SectionId] = (daqResult, adapterBoardResult);
                      await ProcessBusinessLogicAsync(daqResult, adapterBoardResult, cancellationToken);
                  }
                  else
                  {
                      Logger.Warn($"无效数据: SectionId={SectionId}");
                  }
              }
              catch (OperationCanceledException)
              {
                  Logger.Info($"轮询任务取消: SectionId={SectionId}");
                  throw; // 重新抛出,确保调用方知道取消
              }
              catch (IOException ex)
              {
                  Logger.Error($"硬件查询失败: SectionId={SectionId}", ex);
                  await NotifyErrorAsync("硬件通信错误", ex, cancellationToken);
              }
              catch (Exception ex)
              {
                  Logger.Error($"未知错误: SectionId={SectionId}", ex);
                  await NotifyErrorAsync("未知硬件错误", ex, cancellationToken);
              }
          }
      }

5. 在依赖方法中传递 CancellationToken

  • 确保 QueryHardwareDataAsync 和 ProcessBusinessLogicAsync 等方法正确处理 CancellationToken。

  • 示例:

    csharp

    private async Task<(HardwareResult DAQResult, HardwareResult AdapterBoardResult)> QueryHardwareDataAsync(string sectionId, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested(); // 提前检查取消
        Logger.Debug($"查询数据: SectionId={sectionId}");
    
        var daqResult = await QueryDataWithLockAsync(HardwareCommands.DAQRead, sectionId, cancellationToken);
        var adapterBoardResult = (DateTime.UtcNow - _lastAdapterBoardQueryTime) < _cacheExpiration && _cachedAdapterBoardResult != null
            ? _cachedAdapterBoardResult
            : await QueryDataWithLockAsync(HardwareCommands.DAQReadAdapterBoard, sectionId, cancellationToken);
    
        if (adapterBoardResult != null)
        {
            _cachedAdapterBoardResult = adapterBoardResult;
            _lastAdapterBoardQueryTime = DateTime.UtcNow;
        }
    
        return (daqResult, adapterBoardResult);
    }
    
    private async Task<HardwareResult> QueryDataWithLockAsync(string command, string sectionId, CancellationToken cancellationToken)
    {
        if (await HardwareMgr.DAQLocks[sectionId].WaitAsync(TimeSpan.FromSeconds(5), cancellationToken))
        {
            try
            {
                return await _hardware.QueryDataAsync(command, cancellationToken) as HardwareResult;
            }
            finally
            {
                HardwareMgr.DAQLocks[sectionId].Release();
            }
        }
        Logger.Warn($"获取锁超时: SectionId={sectionId}, 命令={command}");
        return null;
    }
  • 注意:

    • QueryDataAsync 需支持 CancellationToken,确保硬件通信可取消。

    • 如果硬件不支持取消,需在超时后记录警告并返回默认值。

6. 系统级传递

  • 在系统级(如 SystemMgr)中,通常需要管理多个 TestChannel 的轮询任务,并统一取消。

  • 示例:

    csharp

    public class SystemMgr
    {
        private readonly List<TestChannel> _channels;
        private CancellationTokenSource _systemCts;
    
        public async Task StartSystemAsync()
        {
            _systemCts = new CancellationTokenSource();
            var tasks = _channels.Select(channel =>
                channel.StartRealtimePollingAsync(TimeSpan.FromMilliseconds(100), _systemCts.Token));
            await Task.WhenAll(tasks);
        }
    
        public async Task StopSystemAsync()
        {
            if (_systemCts != null)
            {
                _systemCts.Cancel();
                try
                {
                    await Task.Delay(1000); // 等待任务优雅退出
                }
                catch (OperationCanceledException)
                {
                    Logger.Info("系统轮询任务已取消");
                }
                _systemCts.Dispose();
                _systemCts = null;
            }
        }
    }
  • 场景:

    • 系统初始化时启动所有通道的轮询。

    • 系统关闭(如 CloseSystemAsync)时统一取消。

7. 用户交互

  • 如果轮询由用户操作触发(如 UI 按钮),可以通过 UI 事件创建和取消 CancellationTokenSource。

  • 示例(WPF/WinForms):

    csharp

    private CancellationTokenSource _cts;
    
    private async void StartPollingButton_Click(object sender, EventArgs e)
    {
        _cts = new CancellationTokenSource();
        try
        {
            await testChannel.StartRealtimePollingAsync(TimeSpan.FromMilliseconds(100), _cts.Token);
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("轮询已取消");
        }
    }
    
    private void StopPollingButton_Click(object sender, EventArgs e)
    {
        _cts?.Cancel();
        _cts?.Dispose();
        _cts = null;
    }

最佳实践

  1. 始终使用 using 或 Dispose:

    • 确保 CancellationTokenSource 在不再需要时释放资源。

    • 示例:using var cts = new CancellationTokenSource();

  2. 捕获 OperationCanceledException:

    • 在调用方和方法内部捕获取消异常,避免未处理异常。

    • 示例:catch (OperationCanceledException) { Logger.Info("任务取消"); }

  3. 传递链路完整:

    • 确保 CancellationToken 从调用方传递到所有依赖的异步方法(如 QueryDataAsync、Task.Delay)。

    • 示例:await Task.Delay(100, cancellationToken);

  4. 超时机制:

    • 为长时间运行的任务设置超时,防止无限等待。

    • 示例:var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));

  5. 日志记录:

    • 记录取消事件和异常,方便调试。

    • 示例:Logger.Info($"轮询任务取消: SectionId={SectionId}");

  6. 测试取消逻辑:

    • 编写单元测试验证取消行为。

    • 示例:

      csharp

      [Fact]
      public async Task StartRealtimePolling_Cancel_StopsPolling()
      {
          var cts = new CancellationTokenSource();
          var channel = new TestChannel(new Mock<IHardwareAdaptor>().Object, new Mock<IConfigProvider>().Object);
          var task = channel.StartRealtimePollingAsync(TimeSpan.FromMilliseconds(100), cts.Token);
          cts.Cancel();
          await Assert.ThrowsAsync<OperationCanceledException>(() => task);
      }

传递 CancellationToken 的完整示例

以下是结合系统管理和用户交互的完整代码,展示如何传递 CancellationToken:

csharp

using log4net;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class TestChannel
{
    private static readonly ILog Logger = LogManager.GetLogger(typeof(TestChannel).Name);
    private readonly IHardwareAdaptor _hardware;
    private readonly IConfigProvider _configProvider;
    private readonly ConcurrentDictionary<string, (HardwareResult DAQResult, HardwareResult AdapterBoardResult)> _dataCache = new();
    private DateTime _lastAdapterBoardQueryTime;
    private HardwareResult _cachedAdapterBoardResult;
    private readonly TimeSpan _cacheExpiration = TimeSpan.FromSeconds(10);

    private static class HardwareCommands
    {
        public const string DAQRead = "DAQREAD ";
        public const string DAQReadAdapterBoard = "DAQREADADAPTERBOARD ";
    }

    public TestChannel(IHardwareAdaptor hardware, IConfigProvider configProvider)
    {
        _hardware = hardware;
        _configProvider = configProvider;
    }

    public async Task StartRealtimePollingAsync(TimeSpan interval, CancellationToken cancellationToken)
    {
        Logger.Info($"启动实时轮询: SectionId={SectionId}, 间隔={interval.TotalMilliseconds}ms");
        using var timer = new PeriodicTimer(interval);
        while (await timer.WaitForNextTickAsync(cancellationToken))
        {
            try
            {
                var (daqResult, adapterBoardResult) = await QueryHardwareDataAsync(SectionId, cancellationToken);
                if (daqResult != null && adapterBoardResult != null)
                {
                    _dataCache[SectionId] = (daqResult, adapterBoardResult);
                    await ProcessBusinessLogicAsync(daqResult, adapterBoardResult, cancellationToken);
                }
                else
                {
                    Logger.Warn($"无效数据: SectionId={SectionId}");
                }
            }
            catch (OperationCanceledException)
            {
                Logger.Info($"轮询任务取消: SectionId={SectionId}");
                throw;
            }
            catch (IOException ex)
            {
                Logger.Error($"硬件查询失败: SectionId={SectionId}", ex);
                await NotifyErrorAsync("硬件通信错误", ex, cancellationToken);
            }
            catch (Exception ex)
            {
                Logger.Error($"未知错误: SectionId={SectionId}", ex);
                await NotifyErrorAsync("未知硬件错误", ex, cancellationToken);
            }
        }
    }

    private async Task<(HardwareResult DAQResult, HardwareResult AdapterBoardResult)> QueryHardwareDataAsync(string sectionId, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        Logger.Debug($"查询数据: SectionId={sectionId}");

        var daqResult = await QueryDataWithLockAsync(HardwareCommands.DAQRead, sectionId, cancellationToken);
        var adapterBoardResult = (DateTime.UtcNow - _lastAdapterBoardQueryTime) < _cacheExpiration && _cachedAdapterBoardResult != null
            ? _cachedAdapterBoardResult
            : await QueryDataWithLockAsync(HardwareCommands.DAQReadAdapterBoard, sectionId, cancellationToken);

        if (adapterBoardResult != null)
        {
            _cachedAdapterBoardResult = adapterBoardResult;
            _lastAdapterBoardQueryTime = DateTime.UtcNow;
        }

        Logger.Debug($"查询结果: DAQ={daqResult?.ResultMap["Voltage"]}, AdapterBoardTemp={adapterBoardResult?.ResultMap["AdapterBoardTemp"]}");
        return (daqResult, adapterBoardResult);
    }

    private async Task<HardwareResult> QueryDataWithLockAsync(string command, string sectionId, CancellationToken cancellationToken)
    {
        if (await HardwareMgr.DAQLocks[sectionId].WaitAsync(TimeSpan.FromSeconds(5), cancellationToken))
        {
            try
            {
                return await _hardware.QueryDataAsync(command, cancellationToken) as HardwareResult;
            }
            finally
            {
                HardwareMgr.DAQLocks[sectionId].Release();
            }
        }
        Logger.Warn($"获取锁超时: SectionId={sectionId}, 命令={command}");
        return null;
    }

    private async Task ProcessBusinessLogicAsync(HardwareResult daqResult, HardwareResult adapterBoardResult, CancellationToken cancellationToken)
    {
        // 业务判定逻辑(参考之前的优化)
        await Task.CompletedTask;
    }

    private async Task NotifyErrorAsync(string message, Exception ex, CancellationToken cancellationToken)
    {
        SystemNotifEvent.HardErrorNotifEvent(this, message, ex.Message);
        await Task.CompletedTask;
    }

    public string SectionId { get; set; }
}

public class SystemMgr
{
    private static readonly ILog Logger = LogManager.GetLogger(typeof(SystemMgr).Name);
    private readonly List<TestChannel> _channels = new();
    private CancellationTokenSource _systemCts;

    public async Task StartSystemAsync()
    {
        _systemCts = new CancellationTokenSource();
        var interval = TimeSpan.FromMilliseconds(100);
        var tasks = _channels.Select(channel =>
            channel.StartRealtimePollingAsync(interval, _systemCts.Token));
        try
        {
            await Task.WhenAll(tasks);
        }
        catch (OperationCanceledException)
        {
            Logger.Info("所有轮询任务已取消");
        }
    }

    public async Task StopSystemAsync()
    {
        if (_systemCts != null)
        {
            _systemCts.Cancel();
            try
            {
                await Task.Delay(1000); // 等待任务退出
            }
            catch (OperationCanceledException)
            {
                Logger.Info("系统停止完成");
            }
            _systemCts.Dispose();
            _systemCts = null;
        }
    }
}

总结

  • 创建:使用 CancellationTokenSource 创建 CancellationToken,通过 cts.Token 传递。

  • 传递:将 CancellationToken 作为参数传递给 StartRealtimePollingAsync 及依赖方法。

  • 取消:通过 cts.Cancel() 触发取消,捕获 OperationCanceledException 处理退出逻辑。

  • 系统级:在 SystemMgr 中统一管理多个通道的 CancellationTokenSource,支持系统级取消。

  • 用户交互:通过 UI 事件(如按钮点击)创建和取消 CancellationTokenSource。

如果需要进一步定制(如特定取消场景、硬件协议支持取消的实现),请提供更多细节,我可以提供更具体的方案!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhxup606

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值