報錯前因後果:
我現在使用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);
}
}