记录一次定时器报错

报错前因后果:
我现在使用Winform开发上位机程序,读取PLC传递过来的CT,
1、我将定时器方法InitTimerTick();写在构造器或者Load事件起作用
2、如果写在后台线程不起作用,也不报错,我打断点查询的时候,发现InitCommonRegion方法没有执行,我向上查找,最终断点打在 timer.Tick += new EventHandler(Timer_Tick);
3、如果后台线程使用Task,那么程序直接跳出去了,如果程序使用Thread,那么VS2022直接卡死,注意我InitCommonRegion方法非UI线程的数据赋值给UI线程的控件已经使用了

this.Invoke(new Action(() => {
                         this.uiTextBox1.Text = result.ToString();
                     }));

我们最好将定时器写在Load事件,防止控件还没有初始化,定时器已经开始执行。
后台线程不起作用不报错的原因可能是因为InitCommonRegion()方法中使用了await和Task.Run,这会让该方法变成异步方法,调用InitCommonRegion()方法的线程会等待执行完成才继续往下执行。而在后台线程中,由于没有消息循环,异步方法会导致线程无法执行完毕,从而无法更新UI界面,导致程序看起来没有响应。而在调试模式下,由于断点的存在会暂停异步方法的执行,从而让后台线程有机会执行InitCommonRegion()方法,并且在断点处报错。

至于为什么使用Thread会将VS卡死,可能是因为在创建Thread时需要传入ThreadStart委托,而该委托必须是一个无参数的方法,因此无法向该方法中传递参数,从而导致Thread无法调用InitCommonRegion()方法,从而陷入死循环,最终导致程序无响应。而使用Task则可以通过Task.Run的方式传递参数并启动异步方法。

建议使用异步方法来读取PLC数据,并且使用await避免程序阻塞,可以避免上述问题的发生。同时,在异步方法中也可以使用Task.Delay方法来实现定时器的效果,从而避免使用Timer带来的问题。
我最后的解决方案是取消了定时器的使用改为while true

  /// <summary>
  /// 工作线程
  /// </summary>
  /// <exception cref="NotImplementedException"></exception>
  private async void ExecuteWork()
  {
      while (!workMre.WaitOne(100)) 
      {
         await InitCommonRegion();
      }       
  }

问题代码如下 ,可以借鉴参考

public partial class Main_Form : UIForm
{
    IReadService _readService;
    IPlcService _plcService;
    IMesService _mesService;
    IServiceProvider _service;
    private ManualResetEvent helpMre = new ManualResetEvent(false);
    private ManualResetEvent workMre = new ManualResetEvent(false);
    private ManualResetEvent dataRefreshMre = new ManualResetEvent(false);
    public Main_Form(IReadService readService ,IPlcService plcService,IMesService mesService, IServiceProvider service)
    {
        _plcService = plcService;
        _mesService = mesService;
        _service = service;
        _readService = readService;

        InitializeComponent();
        this.Load += Main_Form_Load;
     //  InitTimerTick();
    }

    private void Main_Form_Load(object sender, System.EventArgs e)
    {
        Task workTask = new Task(ExecuteWork);
        workTask.Start();

        Task dataRefreshTask = new Task(ExecuteDataRefresh);
        dataRefreshTask.Start();

        Task helpTask = new Task(ExecuteHelper);
        helpTask.Start();
        // InitTimerTick();
        /* await Task.Run(() =>
         {
             InitTimerTick();
         });*/

        Thread thread = new Thread(Execute);
        thread.IsBackground = true;
        thread.Start();
    }

    private void Execute()
    {
        InitTimerTick();
    }

    /// <summary>
    /// 工作线程
    /// </summary>
    /// <exception cref="NotImplementedException"></exception>
    private void ExecuteWork()
    {
        while (!workMre.WaitOne(100)) 
        {
        } 
    }

    /// <summary>
    /// 数据传递线程
    /// </summary>
    /// <exception cref="NotImplementedException"></exception>
    private void ExecuteDataRefresh()
    {
        while (!dataRefreshMre.WaitOne(1000)) { }
    }

    /// <summary>
    /// 后台辅助线程
    /// </summary>
    /// <exception cref="NotImplementedException"></exception>
    private void ExecuteHelper()
    {
        while (!helpMre.WaitOne(500)) { }
    }
    private void InitTimerTick()
    {
        // 创建Timer实例
        Timer timer = new Timer();
        // 设置触发间隔时间,例如1秒
        timer.Interval = 500;
        // 订阅Tick事件
        timer.Tick += new EventHandler(Timer_Tick);
        // 启动Timer
        timer.Start();
    }

    private async Task InitCommonRegion()
    {
        try
        {
            await Task.Run(async () => {
                // 你的耗时PLC读取操作
                var device01 = new Device01
                {
                    // ... 初始化 device01 ...
                    SlaveId=1
                };
                var result =await _plcService.ReadCoilsAsync(device01, 0, 5);//.Result;
                var s= (result.Result)as bool[] ;
              
                // 检查是否有访问UI线程的权限
                if (this.uiTextBox1.InvokeRequired)
                {
                    // 使用 Invoke 确保在UI线程上更新控件
                    this.Invoke(new Action(() => {
                        this.uiTextBox1.Text = s[0].ToString();
                    }));
                }
                else
                {
                    // 如果已经在UI线程上,则直接更新控件
                    this.uiTextBox1.Text = "没有数据";
                }
            });
        }
        catch (Exception e)
        {
            MessageBox.Show($"{e.Message}");
        }
    }

    private async void Timer_Tick(object sender, EventArgs e)
    {
       await  InitCommonRegion();              //使用定时器实时读取公共区数据
    }

    private async void UpLoad_Load(object sender, System.EventArgs e)
    {
        // 假设这是在窗口加载时执行
        bool success = await _mesService.UploadDataToMesAsync(new UpLoadData());
        if (success)
        {
            MessageBox.Show("数据上传成功");
        }
        else
        {
            MessageBox.Show("数据上传失败");
        }
    }

    // 确保在窗体关闭时设置_isRunning为false,以结束线程
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _isRunning = false; // 设置标志,结束线程循环
        base.OnFormClosing(e);
       
    }
}
posted @ 2024-05-16 17:15  孤沉  阅读(5)  评论(0编辑  收藏  举报