c# 线程的基础教学(winform 电梯模拟)
目录
案例一: 启动与停止
案例二: 加热装置
案例三:电梯
在图形用户界面中,主线程负责界面更新,如果只在主线程中运行任务,容易造成UI卡顿。
案例一: 启动与停止
创建winform 项目
在界面中添加2个button
并设计点击事件,start按钮点击后会切换颜色。
我们定义一个action1(),一个简单的循环。
代码如下:
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading; namespace WindowsFormsApp4
{public partial class Form1 : Form{private static bool istart = false;//start 按钮是否按下private static bool istop = false;//stop 按钮是否按下public Form1(){InitializeComponent();}private void button_start_Click(object sender, EventArgs e){istart = !istart;if (istart){button_start.BackColor = Color.Green;MessageBox.Show("已启动");action1();}else {button_start.BackColor = Color.White;}}private void button_stop_Click(object sender, EventArgs e){istop = !istop;}private void action1(){while (istart){if (istop){break;} }}}}
运行,action1被成功调用了,但我们没法关闭它,UI界面卡住了。
原因很简单。
WinForms的UI操作必须在主线程(UI线程)执行,而while(istart)
循环在UI线程中无限运行,导致:
- 界面无法刷新(按钮状态不更新)
- 无法处理其他用户输入(如点击STOP按钮)
我们使用,线程的方式运行action1:
Thread th_action1 = new Thread(action1);
th_action1.Start();
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading; namespace WindowsFormsApp4
{public partial class Form1 : Form{private static bool istart = false;//start 按钮是否按下private static bool istop = false;//stop 按钮是否按下public Form1(){InitializeComponent();}private void button_start_Click(object sender, EventArgs e){istart = !istart;if (istart){button_start.BackColor = Color.Green;Thread th_action1 = new Thread(action1);th_action1.Start();}else {button_start.BackColor = Color.White;}}private void button_stop_Click(object sender, EventArgs e){istop = !istop;}private void action1(){while (istart){if (istop){button_start.BackColor = Color.White;break;} }}}}
运行后,可以正常使用start 和 stop 按钮。
案例二: 加热装置
在案例1的基础上,添加textbox1
在action1 中,不断增加水温
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading; namespace WindowsFormsApp4
{public partial class Form1 : Form{private static bool istart = false;//start 按钮是否按下private static bool istop = false;//stop 按钮是否按下private static int temperature = 0;public Form1(){InitializeComponent();}private void button_start_Click(object sender, EventArgs e){istart = !istart;if (istart){button_start.BackColor = Color.Green;Thread th_action1 = new Thread(action1);th_action1.Start();}else {button_start.BackColor = Color.White;}}private void button_stop_Click(object sender, EventArgs e){istop = !istop;}private void action1(){while (istart){temperature = temperature + 1;textBox1.Text = temperature.ToString();Thread.Sleep(500);if (istop){button_start.BackColor = Color.White;break;} }}}}
运行,
成功报错。
action1 已经进入新线程th_action1,,而操作ui我们得回主线程。
所以,做如下修改:
private void action1()
{
while (istart)
{
temperature = temperature + 1;
//textBox1.Text = temperature.ToString();//报错
//this.BeginInvoke(new Action(() => { textBox1.Text = temperature.ToString(); }));//法一
this.Invoke((MethodInvoker)delegate {textBox1.Text = temperature.ToString();});//法二
Thread.Sleep(1500);
if (istop)
{
break;
}
}
}
其中,Invoke
是同步的,BeginInvoke
是异步的。
也就是说,使用Invoke
时,如果UI线程正忙,调用action1会被阻塞。使用BeginInvoke
时,会继续执行后续代码,而委托会在UI线程的消息循环中排队等待执行。
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading; namespace WindowsFormsApp4
{public partial class Form1 : Form{private static bool istart = false;//start 按钮是否按下private static bool istop = false;//stop 按钮是否按下private static int temperature = 0;public Form1(){InitializeComponent();}private void button_start_Click(object sender, EventArgs e){istart = !istart;if (istart){button_start.BackColor = Color.Green;Thread th_action1 = new Thread(action1);th_action1.Start();}else {button_start.BackColor = Color.White;}}private void button_stop_Click(object sender, EventArgs e){istop = !istop;}private void action1()//加热{while (istart){temperature = temperature + 1;//textBox1.Text = temperature.ToString();//报错//this.BeginInvoke(new Action(() => { textBox1.Text = temperature.ToString(); }));//法一this.Invoke((MethodInvoker)delegate {textBox1.Text = temperature.ToString();});//法二Thread.Sleep(1000);if (istop){button_start.BackColor = Color.White;istop = false;break;} }}}}
现在,我们加入室温的降温(每三秒降低1度)。
private void action2()//降温
{
while (true) {
if (temperature > 0) {
temperature = temperature - 1;
this.BeginInvoke(new Action(() => { textBox1.Text = temperature.ToString(); }));
Thread.Sleep(3000);
}
}
}
在加载form1时 就开始运行。
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading; namespace WindowsFormsApp4
{public partial class Form1 : Form{private static bool istart = false;//start 按钮是否按下private static bool istop = false;//stop 按钮是否按下private static int temperature = 0;public Form1(){InitializeComponent();Thread th_action2 = new Thread(action2);th_action2.Start();}private void button_start_Click(object sender, EventArgs e){istart = !istart;if (istart){button_start.BackColor = Color.Green;Thread th_action1 = new Thread(action1);th_action1.Start();}else {button_start.BackColor = Color.White;}}private void button_stop_Click(object sender, EventArgs e){istop = !istop;}private void action1()//加热{while (istart){temperature = temperature + 1;//textBox1.Text = temperature.ToString();//报错//this.BeginInvoke(new Action(() => { textBox1.Text = temperature.ToString(); }));//法一this.Invoke((MethodInvoker)delegate {textBox1.Text = temperature.ToString();});//法二Thread.Sleep(1000);if (istop){button_start.BackColor = Color.White;istop = false;break;} }}private void action2()//降温{while (true) {if (temperature > 0) {temperature = temperature - 1;this.BeginInvoke(new Action(() => { textBox1.Text = temperature.ToString(); }));Thread.Sleep(3000);}}}}}
随着action越来越多,逻辑越来越复杂,这种写法并不规范。
对此,做出修改
1.使用lock同步机制保护共享变量_temperature,降低多线程抢变量风险。
2.采用async/await模式
完整代码如下:
using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace WindowsFormsApp4
{public partial class Form1 : Form{private bool _istart;private int _temperature;private readonly object _tempLock = new object();private CancellationTokenSource _cts;public Form1(){InitializeComponent();Load += Form1_Load;}private async void Form1_Load(object sender, EventArgs e){await Task.Run(() => action2()); // 启动降温任务}private void button_start_Click(object sender, EventArgs e){_istart = !_istart;if (_istart){_cts = new CancellationTokenSource();button_start.BackColor = Color.Green;_ = action1(_cts.Token); // 启动加热任务}else{_cts?.Cancel();button_start.BackColor = Color.White;}}private async Task action1(CancellationToken token){while (!token.IsCancellationRequested){lock (_tempLock){_temperature++;}UpdateTemperatureText();try{await Task.Delay(1000, token);}catch (TaskCanceledException){break;}}}private async Task action2(){while (true){lock (_tempLock){if (_temperature > 0) _temperature--;}UpdateTemperatureText();await Task.Delay(3000);}}private void UpdateTemperatureText(){if (textBox1.InvokeRequired){textBox1.Invoke((Action)UpdateTemperatureText);return;}textBox1.Text = _temperature.ToString();}}
}
namespace WindowsFormsApp4
{partial class Form1{/// <summary>/// 必需的设计器变量。/// </summary>private System.ComponentModel.IContainer components = null;/// <summary>/// 清理所有正在使用的资源。/// </summary>/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();}base.Dispose(disposing);}#region Windows 窗体设计器生成的代码/// <summary>/// 设计器支持所需的方法 - 不要修改/// 使用代码编辑器修改此方法的内容。/// </summary>private void InitializeComponent(){this.button_start = new System.Windows.Forms.Button();this.button_stop = new System.Windows.Forms.Button();this.textBox1 = new System.Windows.Forms.TextBox();this.label1 = new System.Windows.Forms.Label();this.SuspendLayout();// // button_start// this.button_start.Location = new System.Drawing.Point(209, 661);this.button_start.Name = "button_start";this.button_start.Size = new System.Drawing.Size(248, 128);this.button_start.TabIndex = 0;this.button_start.Text = "start";this.button_start.UseVisualStyleBackColor = true;this.button_start.Click += new System.EventHandler(this.button_start_Click);// // button_stop// this.button_stop.Location = new System.Drawing.Point(637, 661);this.button_stop.Name = "button_stop";this.button_stop.Size = new System.Drawing.Size(248, 128);this.button_stop.TabIndex = 1;this.button_stop.Text = "stop";this.button_stop.UseVisualStyleBackColor = true;//this.button_stop.Click += new System.EventHandler(this.button_stop_Click);// // textBox1// this.textBox1.Font = new System.Drawing.Font("宋体", 35F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.textBox1.Location = new System.Drawing.Point(526, 245);this.textBox1.Name = "textBox1";this.textBox1.Size = new System.Drawing.Size(121, 87);this.textBox1.TabIndex = 2;this.textBox1.Text = "0";// // label1// this.label1.AutoSize = true;this.label1.Font = new System.Drawing.Font("宋体", 35F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.label1.Location = new System.Drawing.Point(292, 248);this.label1.Name = "label1";this.label1.Size = new System.Drawing.Size(240, 70);this.label1.TabIndex = 3;this.label1.Text = "水温:";// // Form1// this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;this.ClientSize = new System.Drawing.Size(1189, 881);this.Controls.Add(this.label1);this.Controls.Add(this.textBox1);this.Controls.Add(this.button_stop);this.Controls.Add(this.button_start);this.Name = "Form1";this.Text = "Form1";this.ResumeLayout(false);this.PerformLayout();}#endregionprivate System.Windows.Forms.Button button_start;private System.Windows.Forms.Button button_stop;private System.Windows.Forms.TextBox textBox1;private System.Windows.Forms.Label label1;}
}
案例三:电梯
创建c# winform 项目 lift
添加几个button 和 textbox
namespace lift
{partial class Form1{/// <summary>/// Required designer variable./// </summary>private System.ComponentModel.IContainer components = null;/// <summary>/// Clean up any resources being used./// </summary>/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();}base.Dispose(disposing);}#region Windows Form Designer generated code/// <summary>/// Required method for Designer support - do not modify/// the contents of this method with the code editor./// </summary>private void InitializeComponent(){textBox_layer = new TextBox();label1 = new Label();textBox_door = new TextBox();label2 = new Label();button_L1 = new Button();button_L2 = new Button();button_L3 = new Button();button_L4 = new Button();label3 = new Label();SuspendLayout();// // textBox_layer// textBox_layer.Font = new Font("Microsoft YaHei UI", 40F);textBox_layer.Location = new Point(228, 53);textBox_layer.Name = "textBox_layer";textBox_layer.Size = new Size(124, 109);textBox_layer.TabIndex = 0;textBox_layer.Text = "1";// // label1// label1.AutoSize = true;label1.Font = new Font("Microsoft YaHei UI", 18F);label1.Location = new Point(12, 85);label1.Name = "label1";label1.Size = new Size(200, 46);label1.TabIndex = 1;label1.Text = "目前楼层:";// // textBox_door// textBox_door.Font = new Font("Microsoft YaHei UI", 20F);textBox_door.Location = new Point(196, 211);textBox_door.Name = "textBox_door";textBox_door.Size = new Size(170, 58);textBox_door.TabIndex = 3;textBox_door.Text = "CLOSE";// // label2// label2.AutoSize = true;label2.Font = new Font("Microsoft YaHei UI", 18F);label2.Location = new Point(112, 421);label2.Name = "label2";label2.Size = new Size(200, 46);label2.TabIndex = 4;label2.Text = "电梯按钮:";// // button_L1// button_L1.Location = new Point(85, 723);button_L1.Name = "button_L1";button_L1.Size = new Size(82, 72);button_L1.TabIndex = 5;button_L1.Text = "1";button_L1.UseVisualStyleBackColor = true;button_L1.Click += button_L1_Click;// // button_L2// button_L2.Location = new Point(295, 723);button_L2.Name = "button_L2";button_L2.Size = new Size(82, 72);button_L2.TabIndex = 6;button_L2.Text = "2";button_L2.UseVisualStyleBackColor = true;button_L2.Click += button_L2_Click;// // button_L3// button_L3.Location = new Point(85, 543);button_L3.Name = "button_L3";button_L3.Size = new Size(82, 72);button_L3.TabIndex = 7;button_L3.Text = "3";button_L3.UseVisualStyleBackColor = true;button_L3.Click += button_L3_Click;// // button_L4// button_L4.Location = new Point(295, 543);button_L4.Name = "button_L4";button_L4.Size = new Size(82, 72);button_L4.TabIndex = 8;button_L4.Text = "4";button_L4.UseVisualStyleBackColor = true;button_L4.Click += button_L4_Click;// // label3// label3.AutoSize = true;label3.Font = new Font("Microsoft YaHei UI", 18F);label3.Location = new Point(24, 211);label3.Name = "label3";label3.Size = new Size(128, 46);label3.TabIndex = 9;label3.Text = "电梯门";// // Form1// AutoScaleDimensions = new SizeF(11F, 24F);AutoScaleMode = AutoScaleMode.Font;ClientSize = new Size(925, 915);Controls.Add(label3);Controls.Add(button_L4);Controls.Add(button_L3);Controls.Add(button_L2);Controls.Add(button_L1);Controls.Add(label2);Controls.Add(textBox_door);Controls.Add(label1);Controls.Add(textBox_layer);Name = "Form1";Text = "Form1";ResumeLayout(false);PerformLayout();}#endregionprivate TextBox textBox_layer;private Label label1;private TextBox textBox_door;private Label label2;private Button button_L1;private Button button_L2;private Button button_L3;private Button button_L4;private Label label3;}
}
和案例二差不多,使用异步的方式 操作 楼层。
最大的区别是,案例二只有一个start按钮,而这里有多个楼层按钮
当同时按下多个按钮时,多个任务会同时运行,导致currentFloor被多个线程修改,引发竞态。
using System;
using System.Threading.Tasks;
using System.Windows.Forms;namespace lift
{public partial class Form1 : Form{private int currentFloor = 1; // 当前楼层private readonly object floorLock = new object(); public Form1(){InitializeComponent();}private async Task LiftUp()//上升{if (currentFloor < 4){await Task.Delay(2000);lock (floorLock){currentFloor++;}UpdateFloorDisplay();}}private async Task LiftDown()//下降{if (currentFloor > 1){await Task.Delay(2000);lock (floorLock){currentFloor--;}UpdateFloorDisplay();}}private void UpdateFloorDisplay(){if (textBox_layer.InvokeRequired){textBox_layer.BeginInvoke((Action)UpdateFloorDisplay);return;}textBox_layer.Text = currentFloor.ToString();}private async Task LiftRun(int targetFloor)//运行{while (currentFloor != targetFloor){if (targetFloor > currentFloor){await LiftUp();}else{await LiftDown();}}await OpenDoor();}private async Task OpenDoor()//开门{await Task.Delay(1000);if (textBox_door.InvokeRequired){textBox_door.BeginInvoke((Action)(() => textBox_door.Text = "OPEN"));}else{textBox_door.Text = "OPEN";}await Task.Delay(2000);if (textBox_door.InvokeRequired){textBox_door.BeginInvoke((Action)(() => textBox_door.Text = "CLOSE"));}else{textBox_door.Text = "CLOSE";}}private async void button_L1_Click(object sender, EventArgs e){await LiftRun(1);}private async void button_L2_Click(object sender, EventArgs e){await LiftRun(2);}private async void button_L3_Click(object sender, EventArgs e){await LiftRun(3);}private async void button_L4_Click(object sender, EventArgs e){await LiftRun(4);}}
}
为了解决多个按钮打架的问题。
我们只允许 一个事件在运行。
所以需要添加中断,后按的会中断之前的任务。
代码如下:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;namespace lift
{public partial class Form1 : Form{private int currentFloor = 1; // 当前楼层private readonly object floorLock = new object();private CancellationTokenSource _cts;private readonly object _targetLock = new object();private int _targetFloor = 1;public Form1(){InitializeComponent();}private async Task LiftRun(CancellationToken token){while (!token.IsCancellationRequested){int currentTarget;lock (_targetLock){currentTarget = _targetFloor;}if (currentFloor == currentTarget){await OpenDoor();return;}if (currentTarget > currentFloor){await LiftUp(token);}else{await LiftDown(token);}}}// 修改后的上升方法private async Task LiftUp(CancellationToken token){if (currentFloor < 4){await Task.Delay(2000, token); // 使用带取消令牌的延迟token.ThrowIfCancellationRequested();lock (floorLock){currentFloor++;}UpdateFloorDisplay();}}// 修改后的下降方法private async Task LiftDown(CancellationToken token){if (currentFloor > 1){await Task.Delay(2000, token); // 使用带取消令牌的延迟token.ThrowIfCancellationRequested();lock (floorLock){currentFloor--;}UpdateFloorDisplay();}}private async Task OpenDoor()//开门{await Task.Delay(1000);if (textBox_door.InvokeRequired){textBox_door.BeginInvoke((Action)(() => textBox_door.Text = "OPEN"));}else{textBox_door.Text = "OPEN";}await Task.Delay(2000);if (textBox_door.InvokeRequired){textBox_door.BeginInvoke((Action)(() => textBox_door.Text = "CLOSE"));}else{textBox_door.Text = "CLOSE";}}// 统一处理楼层请求的方法private async Task HandleFloorRequest(int requestedFloor){// 更新目标楼层lock (_targetLock){_targetFloor = requestedFloor;}// 取消当前运行的任务_cts?.Cancel();// 创建新的取消令牌var newCts = new CancellationTokenSource();_cts = newCts;try{await LiftRun(newCts.Token);}catch (OperationCanceledException){// 正常取消,无需处理}}private async void button_L1_Click(object sender, EventArgs e){await HandleFloorRequest(1);}private async void button_L2_Click(object sender, EventArgs e){await HandleFloorRequest(2);}private async void button_L3_Click(object sender, EventArgs e){await HandleFloorRequest(3);}private async void button_L4_Click(object sender, EventArgs e){await HandleFloorRequest(4);}private void UpdateFloorDisplay(){if (textBox_layer.InvokeRequired){textBox_layer.BeginInvoke((Action)UpdateFloorDisplay);return;}textBox_layer.Text = currentFloor.ToString();}}
}
运行后:
屏幕录制 2025-08-11 112753
显然,视频中,后按的按钮直接打断了之前的任务。
为了能像现实中的电梯一样有序,进行改进:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;namespace lift
{public partial class Form1 : Form{private int currentFloor = 1;private readonly object stateLock = new object();private readonly SortedSet<int> upRequests = new SortedSet<int>();private readonly SortedSet<int> downRequests = new SortedSet<int>(Comparer<int>.Create((x, y) => y.CompareTo(x)));private bool isMoving = false;private bool doorOpen = false;public Form1(){InitializeComponent();UpdateDisplay();}private void UpdateDisplay(){if (textBox_layer.InvokeRequired){textBox_layer.BeginInvoke((Action)UpdateDisplay);return;}textBox_layer.Text = currentFloor.ToString();textBox_door.Text = doorOpen ? "OPEN" : "CLOSE";}private async Task ElevatorLoop(){while (true){int nextFloor = GetNextDestination();if (nextFloor == -1){isMoving = false;await Task.Delay(100);continue;}isMoving = true;await MoveToFloor(nextFloor);await HandleStop(nextFloor);}}private int GetNextDestination(){lock (stateLock){// 优先处理当前楼层的请求if (upRequests.Contains(currentFloor) || downRequests.Contains(currentFloor)){return currentFloor;}// 根据当前方向选择下一个目标if (upRequests.Count > 0){return upRequests.Min;}if (downRequests.Count > 0){return downRequests.Max;}return -1; // 无请求}}private async Task MoveToFloor(int targetFloor){while (currentFloor != targetFloor){await Task.Delay(1000); // 移动时间缩短为1秒便于测试lock (stateLock){if (currentFloor < targetFloor){currentFloor++;}else{currentFloor--;}}UpdateDisplay();// 检查移动过程中是否有同方向的中间请求await CheckIntermediateStops();}}private async Task CheckIntermediateStops(){lock (stateLock){// 如果是上行且当前楼层有上行请求if (upRequests.Contains(currentFloor)){// 临时移除请求,会在HandleStop中重新添加upRequests.Remove(currentFloor);}// 如果是下行且当前楼层有下行请求else if (downRequests.Contains(currentFloor)){downRequests.Remove(currentFloor);}else{return;}}// 立即处理当前楼层的请求await HandleStop(currentFloor);}private async Task HandleStop(int floor){// 开门doorOpen = true;UpdateDisplay();await Task.Delay(1500); // 开门时间// 处理当前楼层的所有请求lock (stateLock){upRequests.Remove(floor);downRequests.Remove(floor);}// 关门doorOpen = false;UpdateDisplay();await Task.Delay(1000); // 关门时间// 重新评估方向EvaluateDirection();}private void EvaluateDirection(){lock (stateLock){// 如果没有请求了,保持静止if (upRequests.Count == 0 && downRequests.Count == 0){isMoving = false;return;}// 如果有上行请求在当前楼层之上,或下行请求在当前楼层之下isMoving = (upRequests.Count > 0 && upRequests.Min > currentFloor) ||(downRequests.Count > 0 && downRequests.Max < currentFloor);}}private void AddRequest(int floor){if (floor < 1 || floor > 4){MessageBox.Show("无效楼层请求");return;}lock (stateLock){// 根据当前位置和方向决定将请求加入哪个队列if (floor > currentFloor){upRequests.Add(floor);downRequests.Remove(floor); // 确保请求不在两个队列中}else if (floor < currentFloor){downRequests.Add(floor);upRequests.Remove(floor);}else{// 当前楼层的请求立即处理upRequests.Add(floor); // 随便加一个队列,会在HandleStop中处理}// 如果电梯静止,启动它if (!isMoving && !doorOpen){Task.Run(ElevatorLoop);}}}private void button_L1_Click(object sender, EventArgs e) => AddRequest(1);private void button_L2_Click(object sender, EventArgs e) => AddRequest(2);private void button_L3_Click(object sender, EventArgs e) => AddRequest(3);private void button_L4_Click(object sender, EventArgs e) => AddRequest(4);}
}