WPF频繁更新UI卡顿问题

我的WPF程序,需要连接PLC、CCD、RFID、扫码枪、控制卡
所以我写了

  InitHardware();
 private void InitHardware()
 {
     var tasks = new Task[]
     {
         //后台线程长连接,不取消令牌
         Task.Factory.StartNew(() => InitConnPLC(),CancellationToken.None,TaskCreationOptions.LongRunning,TaskScheduler.Default),
         Task.Factory.StartNew(() => InitConnRFId(),CancellationToken.None,TaskCreationOptions.LongRunning,TaskScheduler.Default),
         Task.Factory.StartNew(() => InitConnCCd(),CancellationToken.None,TaskCreationOptions.LongRunning,TaskScheduler.Default),
         Task.Factory.StartNew(() => InitScanCode(),CancellationToken.None,TaskCreationOptions.LongRunning,TaskScheduler.Default),
         Task.Factory.StartNew(() => InitControlCard(),CancellationToken.None,TaskCreationOptions.LongRunning,TaskScheduler.Default),
     };
     Task.Factory.ContinueWhenAll(tasks, completedTasks =>
     {
         // 当所有任务完成时的逻辑
         foreach (var task in completedTasks)
         {
             if (task.IsFaulted)
             {
                 // 处理任务异常
                 Dispatcher.CurrentDispatcher.Invoke(new Action(() =>
                 {
                     HandyOrgMessageBox.Info("Task。PLC连接失败");                      //这段代码先放着,我在其他地方做了异常处理
                 }));
             }
             // 其他所有任务完成时的逻辑
         }
     });
 }
 
  /// <summary>
 /// 初始化PLC,为了应对不同公司的风格
 /// PLC读取Ini文件
 /// </summary>
 public void InitConnPLC()
 {
     PlcClient.Current.PlcCount = SysPlc.PlcCount = IniHelper.Current.GetPath("Config/setting.ini").GetSection("PlcCount").ToInt();
     PlcClient.Current.DefaultPlc = ObPLC.Where(it => it.IP == IPHelper.LocalIP4FromPLC).First(); //如果一个PLC走这条线,用于前期测试,正式发布可以不写DefaultPlc,会自动跟据PlcCount的多少连接
     PlcClient.Current.FindRuningPlc = ObPLC.ToArray(); //如果多个PLC走这条线

     while (true)
     {
         Task.Delay(200);
         Dispatcher.CurrentDispatcher.Invoke(
               new Action(() =>
               {
                   PlcInfo.IsConn = result.IsSuccess;
                   PlcInfo.PlcName = PlcInfo.IsConn ? "已连接PLC" : "未连接PLC";
               })
           );
     }
   
 }

可以看出我在这5个类似的方法中频繁更新UI,因为我要和设备长连接
解决方案1、将更新UI从while中剔除出来,写在UI线程
首先增加一个静态的全局变量

public class SysPlc
{
	public static bool IsConn {get;set;}
}

那么 Dispatcher.CurrentDispatcher.Invoke不必频繁去写。
修改while

  while (true)
  {
      Task.Delay(200);
      var result = PlcClient.Current.ConnectionPLC;
      SysPlc.IsConn  = result.IsSuccess;
  }

然后加入定时器

  _dispatcherTimer = new DispatcherTimer();
  _dispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
  _dispatcherTimer.Tick += OnTick;
  _dispatcherTimer.Start();

在UI线程通过全局静态传递过来的数据更新UI

  private void OnTick(object sender, EventArgs e)
  {
      PlcInfo.PlcName = SysPlc.IsConn ? "已连接PLC" : "未连接PLC";
  }

对于不必要的操作,改为轮询
使用ManualResetEvent结合轮询机制确实适合于读取PLC传来的周期性数据(如CT,即Control Table),特别是当数据更新不是非常频繁,或者你不需要实时响应每次数据变化时。这种方法的优点是实现简单,并且不会持续占用CPU资源。

除此之外,这种模式还适用于以下场景:

后台任务调度:执行一些不需要实时性的任务,例如定期的日志记录、数据备份等。

资源访问同步:当多个线程需要访问同一资源,并且该资源在某一时刻只能由一个线程访问时,可以使用ManualResetEvent来控制访问权限。

异步等待:在异步编程中,当需要等待某个操作完成,但又不想阻塞当前线程时,可以使用ManualResetEvent。

用户界面更新:在UI应用程序中,可以定期检查后台任务的状态,并在适当的时候更新UI元素。

网络通信:在客户端和服务器之间进行周期性的心跳检测,以确保连接的活跃性。

定时器替代:在某些情况下,如果不想使用System.Windows.Threading.DispatcherTimer或其他定时器类,可以使用ManualResetEvent实现简单的定时功能。

条件变量:在多线程编程中,当一个或多个线程需要等待某个条件成立时,可以使用ManualResetEvent作为条件变量。

任务协调:在复杂的任务执行流程中,可以使用ManualResetEvent来协调不同任务的执行顺序。

节能模式:在移动设备或嵌入式系统中,为了节省电量,可以定期唤醒设备执行任务,然后再次进入休眠状态。

模拟硬件行为:在某些模拟或测试环境中,可能需要模拟硬件的周期性行为,使用ManualResetEvent可以实现这种周期性的触发。

使用ManualResetEvent时,需要注意以下几点:

确保在适当的时候调用Set方法来触发事件,否则循环可能会无限期地继续。
考虑使用Reset方法来重置事件状态,特别是在循环中需要多次等待时。
避免在高频率更新的场景中使用,因为这可能会导致性能问题或响应延迟。
确保在所有可能的退出点正确地处理和清理资源,例如使用Dispose方法释放ManualResetEvent。
根据你的具体需求和场景,选择最合适的同步和通信机制,以确保程序的效率和响应性。

posted @ 2024-06-23 10:08  孤沉  阅读(297)  评论(0编辑  收藏  举报