记录一次定时器报错
报错前因后果:
我现在使用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);
}
}