
单线程会卡主线程,此时会将ui界面给卡住。而多线程开启以后就好了 不会卡住主线程,且运行速度快,相当于多个同时运动。

单线程按钮
private void singlethread_Click(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
//委托 这样调用就和直接调用函数没有区别
//这样做的本质就相当于将方法包装了下 然后直接调用
Action action = () => this.run("单线程");//lambda
action.Invoke();//直接invoke 就是同步
//this.run(); 就等于上面这两步
}
Debug.WriteLine( "****************************");
}
多线程按钮
//多线程测试
/*1、单线程卡界面,多线程不卡界面
* 线程就是执行流——卡界面就表示线程在忙碌 所以界面卡出了(单线程)——卡的是主线程(UI线程)
* 多线程不卡界面——UI线程闲置了——因为把计算任务交给了其他线程完成——通过Task.Run实现——所以不卡界面了
*
*2、单线程慢 多线程快——但是资源占用的计算机资源也多(比如占用cpu资源,也就是如果将CPU占用100%了 再多线程也不会更快了)
*
*3、使用多线程过多 可能会导致系统卡死 而task就不会出现这种情况 因为其是原自线程池的 占用的线程数量不超过CPU的核数*3 应该就没有问题
*
*4、多线程的无序性——当然是不好的——出现原因
* 因为线程是计算机资源 需要计算机给他分配 而计算机需要给电脑所有进程分配线程 —— 所以多线程的启动顺序是无序的
* 我们通过相隔时间久来控制启动顺序,如果时间很久还行(但可能因为某些原因导致阻塞了啥的,导致时间就也不行),但是如果控制的时间不是很大 就不行
* 因此通过控制时间间隔控制启动顺序也不是一个好方法
*
*/
private void multithreading_Click(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
string name = $"第{i}个多线程";
Action action = () => this.run(name);//lambda
//Action action = () => this.run( $"第{i}个多线程"); 因此这样写就会出错(创建的线程会有多个名字相同的) 这就涉及到了多线程临时变量的问题
/*
* Task.Run这里会一瞬间就过了 具体创建时交给task线程去完成的
* task线程启动时间是不确定的 因此第二种写法等到具体启动的时候 i值就已经发生了变化了
* 因此就会面临临时变量的问题了
*/
Task.Run(action);//这样就是多线程了 本质是向操作系统要个系统 然后将这个事交给他了 然后就不用管了
}
}
运行函数
private void run(string name)
{
long sum=0;
Debug.WriteLine(name+"开始");//using System.Diagnostics;
Debug.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10);//是同步延迟 会阻塞线程 不能取消 实质是使当前线程休眠给定时间。
// Task.Delay(10);//异步延时 不会阻塞线程 实质创建一个运行给定时间的任务
}
Debug.WriteLine(name + "结束");
}

按钮
private void MultiThreadSync_Click(object sender, EventArgs e)
{
//通过下面这段代码 打印的信息就可以看出线程的无序性
//打印的顺序并是不第一条 第二条 第三条这样
List<Task> tasklist = new List<Task>();
tasklist.Add(Task.Run(() => this.coding("第一条")));
tasklist.Add(Task.Run(() => this.coding("第二条")));
tasklist.Add(Task.Run(() => this.coding("第三条")));
tasklist.Add(Task.Run(() => this.coding("第四条")));
/* 1
* 这块是会阻塞线程的
//阻塞到任意一个task完成——不卡界面的特性丢失了,因为主线程被阻塞在了这里
Task.WaitAll(tasklist.ToArray());
Debug.WriteLine("完成了其中任意一条");
//阻塞到全部task都完成——不卡界面的特性丢失了,因为主线程被阻塞在了这里
Task.WaitAll(tasklist.ToArray());
Debug.WriteLine("全部完成");
*/
/* 2
//不要阻塞主线程,但是又能在全部/任意任务完成后,执行一个动作——比如写日志
//使用方法 就是使用Task.Run将上面那段包进去 就是将这其中的动作交给一个线程(就会阻塞这个线程)来完成 所以主线程就不卡了
//但是不推荐用这个 因为包来包去 容易晕 包了好几层以后就很难看懂了 两层还好
Task.Run(() =>
{
Task.WaitAll(tasklist.ToArray());
Debug.WriteLine("完成了其中任意一条");
Task.WaitAll(tasklist.ToArray());
Debug.WriteLine("全部完成");
});
*/
//这样就不会阻塞了
tasklist[0].ContinueWith(t => Debug.WriteLine("第一个多线程的回调函数"));//task的回调
Task.Factory.ContinueWhenAny(tasklist.ToArray(),t => Debug.WriteLine("任意一个多线程完成的回调函数"));//任意一个完成的回调函数
Task.Factory.ContinueWhenAll(tasklist.ToArray(), t => Debug.WriteLine("全部多线程完成的回调函数"));//全部完成的回调函数
}
private void coding(string name)
{
Debug.WriteLine("多线程同步"+name + "开始");//using System.Diagnostics;
Debug.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);//是同步延迟 会阻塞线程 不能取消 实质是使当前线程休眠给定时间。
// Task.Delay(10);//异步延时 不会阻塞线程 实质创建一个运行给定时间的任务
}
Debug.WriteLine("多线程同步" + name + "结束");
}
视频
也就是主界面上的倒计时 显示放在后台 不然一直卡着 会卡死主界面的、
知识要点:


Form1_Load
private void Form1_Load(object sender, EventArgs e)
{
/*
//这样显示不是一直都在 因为界面是会刷新的 刷新了就没有了 因此将其写到重绘事件里面
Graphics g=panel1.CreateGraphics();//在panel1里面创建画布
g.DrawEllipse(new Pen(Color.Green),0,0,40,40);//画圆
*/
}
OnPaint 应该每次刷新界面都会调用 用来不断的将画的图 画到界面上
Graphics g;
//窗体自带一个OnPaint事件用来重绘winform的界面
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
g = panel1.CreateGraphics();//在panel1里面创建画布
g.DrawEllipse(new Pen(Color.Green), 0, 0, 40, 40);//画圆外壳 其实不用画外壳 直接画填充就行了
Brush brush = new SolidBrush(Color.Green);
g.FillEllipse(brush, 0, 0, 40, 40);
}
开始按钮 判断是否打开 然后打开后台线程
int count = 10;
private void LightStartbut_Click(object sender, EventArgs e)
{
/*
//这样就可以实现界面 红绿灯倒数值不断减1 但是会将界面卡死
// 解决方法 使用后台线程去控制计算
while (true)
{
count--;
label2.Text = count.ToString();
//停留1s
Thread.Sleep(1000);
//刷新界面
Update();
}*/
//线程运行结束了 就需要重新开启
//判断线程是否已经被开启
if(!backgroundWorker1.IsBusy)
{
//应该是需要启动起来才能用,不是创建了就能用的
//开启后台线程运行
backgroundWorker1.RunWorkerAsync();//无法同时开启多个 同一时间只能被开启一次
}
}
后台进行处理 双击backgroundWorker控件就可以进入
//后台线程 可以用在一些复杂计算 死循环 会卡死主界面的操作时
//后台线程控件具体使用DoWork来进行计算
//其他线程控制主线程需要使用 invoke 方法
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
//这里是需要加while的 不然运行一遍就出去了
while (true)
{
count--;
try
{
//invoke 方法 会在当前的这个线程里面 往上查找 直到找到当前控件所在的线程 给他的值进行更新
//实现了 不同线程调用另一个主线程控件改变值的方法
Invoke(new Action(() =>
{
label1.Text = count.ToString();
//刷新界面
Update();
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
if(count<4&&count>0)
{
g.DrawEllipse(new Pen(Color.Yellow), 0, 0, 40, 40);//画圆外壳 其实不用画外壳 直接画填充就行了
Brush brush = new SolidBrush(Color.Yellow);
g.FillEllipse(brush, 0, 0, 40, 40);
}
else if(count<=0)
{
g.DrawEllipse(new Pen(Color.Red), 0, 0, 40, 40);//画圆外壳 其实不用画外壳 直接画填充就行了
Brush brush = new SolidBrush(Color.Red);
g.FillEllipse(brush, 0, 0, 40, 40);
break;
}
//Invoke(new EventHandler(delegate
//{
// label1.Text = count.ToString();
// //刷新界面
// Update();
//}));
//停留1s
Thread.Sleep(1000);
}
}
关闭之后将后台线程关闭
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
//这样就可以在后台进程运行是 关闭程序都没有问题了
//在做关闭之前 需要先在属性界面选择可以支持取消这个功能
backgroundWorker1.CancelAsync();
}

//委托 这样调用就和直接调用函数没有区别
//这样做的本质就相当于将方法包装了下 然后直接调用
Action action = () => this.run();//lambda
Task.Run(action);
public void run()
{
try
{
//invoke 方法 会在当前的这个线程里面 往上查找 直到找到当前控件所在的线程 给他的值进行更新
//实现了 不同线程调用另一个主线程控件改变值的方法
Invoke(new Action(() =>
{
//在Invoke这个里面使用 sleep 也是会卡死主线程的
//如果不想卡死主线程,可以将其放到Invoke外面
Thread.Sleep(2000);
uiButton2.Enabled = true;
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
}
或者这样写
Task.Run(async () =>
{
while (true )
{
await Task.Delay(1000);
//3、读取寄存器数据 并写入NitextBox1中
ushort[] values = master.ReadHoldingRegisters(1, 0, 1);//光标放在函数上面会显示要填的参数的含义。slaveID 0号寄存器 读1个 这个函数返回的是一个uchort数组
try //通过断点调试 发现了卡在了这里。通过try和catch的方式,就将出现的异常捕获了
{
NitextBox1.Text = values[0].ToString();//NitextBox1就是第一个textBox的name
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);//错误的原因是进程间操作无效 使用一个简单的操作 就是关闭检测进程间的合法性
}
}
});