『7x24小时有问必答』

一、async/await 核心(必懂)

核心本质:异步不阻塞线程,工控里用来处理串口 / 网口通信、PLC 数据读写、数据库查询,不卡 UI / 主程序关键规则(3 条铁律,记死)
async 修饰方法,返回值只能是 void(少用)、Task(无返回)、Task<t>(有返回)、ValueTask<t>
await 必须跟在 awaitable 类型(Task/ValueTask 等)前,只能在 async 方法里用
异步方法名统一加 Async 后缀(规范)
通俗理解(工控比喻)
同步:主线程去读 PLC,站那等 10 秒,期间啥也干不了(卡界面)
异步:主线程派个小弟(子任务)去读 PLC,自己继续刷新界面 / 处理其他信号,小弟读完回来唤醒主线程
基础实用代码(工控常用)
// 异步读PLC(有返回值)public  async  Task<bool>  ReadPlcDataAsync(string  ip,  int  address){       // await 后跟耗时操作:TCP通信/串口读写,不阻塞主线程       var  client =  new  TcpClient();       await  client.ConnectAsync(ip,  102);  // 异步连接PLC,核心await       // 后续读写逻辑...       return  true;}// 调用异步方法(3种正确方式)// 方式1:UI/主线程调用(推荐,不卡)private  async  void  Btn_ReadPlc_Click(object  sender, EventArgs e){       bool  result =  await  ReadPlcDataAsync("192.168.0.1",  0);}// 方式2:后台任务调用Task.Run(async  ()=>  await  ReadPlcDataAsync("192.168.0.1",  0));// 方式3:不等待(不推荐,丢结果)_ = ReadPlcDataAsync("192.168.0.1",  0);

二、Task vs ValueTask(工控选型关键)

核心区别:内存开销 + 适用场景,工控优先选 ValueTask(高频小任务)
Task:引用类型,存在堆上,有固定内存开销,适合低频、耗时久的任务(如 PLC 批量下载程序、大文件读写)
ValueTask<t>:值类型,栈上分配,开销极小,适合高频、耗时短的任务(如每秒读 1 次 PLC 寄存器、串口实时收数)选型口诀(工控直接套)
高频小任务(1 秒多次):用 ValueTask<t>
低频长任务(几秒 / 几分钟 1 次):用 Task<t>
避免:高频场景用 Task(堆内存频繁分配回收,工控程序长期运行会内存飙升)
实用代码对比
// 高频读PLC寄存器(用ValueTask,推荐)public  async  ValueTask<int>  ReadPlcRegisterAsync(int  regAddr){       // 模拟PLC寄存器快速读取(耗时短、高频调用)       await  Task.Delay(10);  // 模拟通信延迟       return  new  Random().Next(0,  1000);}// 低频批量写PLC(用Task)public  async  Task  WritePlcBatchAsync(List<int> addrList, List<int> valueList){       await  Task.Delay(1000);  // 模拟批量写入耗时       foreach(var  i  in  addrList)      {             // 批量写逻辑      }}

三、异步流 IAsyncEnumerable(工控批量取数)

核心用途:异步分批返回数据,不用等所有数据加载完再返回,适合 PLC 批量读海量寄存器、日志分批读取,避免一次性加载占满内存关键语法:async + yield return + IAsyncEnumerable<t>,遍历用 await foreach工控实战(批量读 PLC 100 个寄存器,分批返回)
// 异步流:分批读PLC寄存器(避免一次性读100个卡主线程)public  async  IAsyncEnumerable<int>  ReadPlcRegistersAsync(int  startAddr,  int  count){       for  (int  i =  0; i < count; i++)      {             // 异步读单个寄存器(高频小任务用ValueTask)             int  value  =  await  ReadPlcRegisterAsync(startAddr + i);             yield  return  value;  // 分批返回,读一个返一个             await  Task.Delay(5);  // 避免给PLC发请求太快      }}// 调用异步流(必须用await foreach)private  async  void  Btn_BatchRead_Click(object  sender, EventArgs e){       // 读PLC 0-99号寄存器,分批接收       await  foreach  (var  regValue  in  ReadPlcRegistersAsync(0,  100))      {            Console.WriteLine($"寄存器值:{regValue}");             // 实时刷新到UI界面(工控常用)            txtLog.AppendText($"寄存器值:{regValue}\r\n");      }}

四、取消令牌 CancellationToken(工控必用,防卡死)

核心用途:安全取消异步任务,工控里用来处理:用户中途取消 PLC 读写、超时自动取消通信、程序退出时终止后台异步任务,避免任务卡死内存泄漏关键组件(2 个)
CancellationToken:令牌本身,传递给异步任务
CancellationTokenSource:令牌源,控制令牌的取消(调用 Cancel () 方法)2 个关键用法:手动取消、超时自动取消(工控全覆盖)

用法 1:手动取消(用户点击取消按钮)

private  CancellationTokenSource _cts;  // 全局令牌源(工控常用全局变量)// 开始异步读PLC(带取消令牌)private  async  void  Btn_StartRead_Click(object  sender, EventArgs e){      _cts =  new  CancellationTokenSource();  // 初始化令牌源       try      {             // 传递取消令牌到异步任务             await  ReadPlcDataWithCancelAsync("192.168.0.1",  0, _cts.Token);      }       catch  (OperationCanceledException)      {            txtLog.AppendText("PLC读取已取消\r\n");  // 取消异常,正常捕获      }}// 取消按钮(手动取消)private  void  Btn_CancelRead_Click(object  sender, EventArgs e){      _cts?.Cancel();  // 触发取消      _cts?.Dispose();  // 释放资源(工控程序必须释放,避免内存泄漏)}// 带取消令牌的异步任务(核心)public  async  Task  ReadPlcDataWithCancelAsync(string  ip,  int  addr, CancellationToken token){       var  client =  new  TcpClient();       // 给异步操作加取消令牌(关键:支持取消的异步方法都带token参数)       await  client.ConnectAsync(ip,  102, token);

       // 手动检测令牌(针对不支持token的异步操作,工控必加)       while  (true)      {            token.ThrowIfCancellationRequested();  // 检测到取消,抛异常终止任务             int  value  =  await  ReadPlcRegisterAsync(addr);            txtLog.AppendText($"实时值:{value}\r\n");             await  Task.Delay(100, token);      }}

用法 2:超时自动取消(工控最常用,防通信卡死)

// PLC通信超时自动取消(10秒超时,工控标准配置)private  async  void  Btn_TimeoutRead_Click(object  sender, EventArgs e){       // 初始化令牌源,设置10秒超时(自动取消)       using  var  cts =  new  CancellationTokenSource(10000);         try      {             await  ReadPlcDataWithCancelAsync("192.168.0.1",  0, cts.Token);      }       catch  (OperationCanceledException)      {            txtLog.AppendText("PLC读取超时,自动取消\r\n");      }}

五、总结(必看)

不要在 async void 方法里吞异常(会导致程序崩溃),UI 事件外尽量用 Task/Task<t>
高频异步任务(如每秒读 PLC)必须用 ValueTask,禁用 Task(内存暴涨)
取消令牌必须手动释放(using 包裹或 Dispose),工控程序长期运行不释放会内存泄漏

</t></t></t></t></t></t></t>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码