案例1 单线程与多线程同步
单线程做菜
//按钮的线程和ui显示的线程是一个线程
//因为暂停以后主界面就拖动不了
private void singlethread_Click(object sender, EventArgs e)
{
Thread.Sleep(3000);//这个线程要暂停3s 此时主界面窗口是拖动不了的
MessageBox.Show("素菜做好了", "友情提示");
Thread.Sleep(3000);
MessageBox.Show("荤菜做好了", "友情提示");
}
thread 做菜
//这样就不会影响到我们ui线程
//可以直接拖动,也可以点击其他按钮,同时执行其他方法
private void thread_Click(object sender, EventArgs e)
{
//里面的参数是一个委托或者说是一个方法
Thread t = new Thread(() =>
{
Thread.Sleep(3000);//这个线程要暂停3s
MessageBox.Show("素菜做好了", "友情提示");
Thread.Sleep(3000);
MessageBox.Show("荤菜做好了", "友情提示");
});
t.Start();
}
task做菜 更推荐用这个
//更推荐用任务来实现多线程
//task底层使用的也是thread,但是他提供了很多线程相关的管理,比如线程池等 他的性能会更好 管理会更方便,所以推荐使用task
private void TaskBtn_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
Thread.Sleep(3000);//这个线程要暂停3s
MessageBox.Show("素菜做好了", "友情提示");
Thread.Sleep(3000);
MessageBox.Show("荤菜做好了", "友情提示");
});
}
//同时做多道菜,这两个是同时完成的
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
Thread.Sleep(3000);//这个线程要暂停3s
MessageBox.Show("素菜做好了", "友情提示");
});
Task.Run(() =>
{
Thread.Sleep(3000);
MessageBox.Show("荤菜做好了", "友情提示");
});
}
同步完成 包含完成后回调函数
public void cook()
{
Thread.Sleep(4000);
MessageBox.Show("荤菜做好了", "友情提示");
}
private void button2_Click(object sender, EventArgs e)
{
这样写的话 直接跳到 菜都做好了吃饭。 开启了线程不管他有没有执行完毕,直接运行后面的代码了
//Task.Run(() =>
//{
// Thread.Sleep(3000);//这个线程要暂停3s
// MessageBox.Show("素菜做好了", "友情提示");
// Thread.Sleep(3000);
// MessageBox.Show("荤菜做好了", "友情提示");
//});
//MessageBox.Show("菜都做好了,大家快来吃饭", "提示");
使用关键字await以后 程序头会自动添加async(异步方法),就不会卡界面了,与我们的ui线程不是同一个线程了
//await Task.Run(() =>
//{
// Thread.Sleep(3000);//这个线程要暂停3s
// MessageBox.Show("素菜做好了", "友情提示");
// Thread.Sleep(3000);
// MessageBox.Show("荤菜做好了", "友情提示");
//});
上面的线程运行完以后再执行
//MessageBox.Show("菜都做好了,大家快来吃饭", "提示");
List<Task> list = new List<Task>();
list.Add(Task.Run(() =>
{
Thread.Sleep(1000);//这个线程要暂停3s
MessageBox.Show("素菜做好了", "友情提示");
}));
list.Add(Task.Run(() =>
{
cook();
}));
//设置一个回调函数 两个都完成了 执行
Task.WhenAll(list).ContinueWith(t=>
{
MessageBox.Show("菜都做好了,大家快来吃饭", "提示");
});
其中任意一个完成了的回调函数
//Task.WhenAny(list).ContinueWith(t =>
//{
// MessageBox.Show("其中一个完成了");
//});
指定其中一个完成的回调函数
//list[0].ContinueWith(t => {
// MessageBox.Show("第一个完成了");
//});
//先打印这句话,然后再打印做好菜 吃饭啥的
MessageBox.Show("上面那个是回调函数");
}
案例2 后台线程的使用(红绿灯)
也就是主界面上的倒计时 显示放在后台 不然一直卡着 会卡死主界面的、
知识要点:
- 掌握backgroundWorker控件的使用(也就是后台线程)
- 掌握sleep方法暂停线程 也就是睡眠
- 掌握invoke方法 解决不同线程的控件调用问题
- 窗体自带一个OnPaint事件用来重绘winform的界面
- 界面的刷新 使用update方法、显示使用show方法
- 掌握try catch方法 异常捕获就在catch中处理
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();
}
后台线程的使用2
包含了 后台线程的数据传递,开启,取消,运行进度
启动设置
public Form1()
{
InitializeComponent();
//可以直接在属性栏设置
backgroundWorker1.WorkerReportsProgress = true;//我们的精度报告是否允许发送
backgroundWorker1.WorkerSupportsCancellation = true; //是否允许取消
}
dowork事件
//不需要里面再创建一个 不会卡ui线程的
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//新线程 新任务
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 100; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;//更改运行返回状态 并不能取消
break;
}
else
{
Thread.Sleep(50);//这里可以替换为工作代码
worker.ReportProgress(i);//提交了一个报告 就会引发一个事件
}
}
e.Result = e.Argument;
}
启动
//启动按钮
private void button1_Click(object sender, EventArgs e)
{
//线程运行结束了 就需要重新开启
//判断线程是否已经被开启 已经启动了 是不能再启动的
if (!backgroundWorker1.IsBusy)
{
//需要启动起来才能用,不是创建了就能用的
//开启后台线程运行
//backgroundWorker1.RunWorkerAsync();//无法同时开启多个 同一时间只能被开启一次
backgroundWorker1.RunWorkerAsync("ni");//可以有参数 可以没有参数
}
}
取消
//取消按钮
private void button2_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation==true)
{
backgroundWorker1.CancelAsync();
}
}
进度变化事件
//ProgressChanged事件处理程序 在创建backgroundWorker线程上执行 也就是在ui线程中执行的
//这样就不需要写invoke了
//在属性中 事件那里 双击进来
//进度每次发生变化 都会进到这里来 这个事件处理程序是异步调用的
//ProgressChanged事件处理程序 在创建backgroundWorker线程上执行 也就是在ui线程中执行的
//这样就不需要写invoke了
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label1.Text=(e.ProgressPercentage.ToString()+"%");
}
处理结束事件
//所有事情都处理完了以后的处理
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled==true)
{
label2.Text = "被取消了";
}
else if (e.Error!=null)
{
label2.Text = "错误"+e.Error.Message;
}
else
{
label2.Text = "完成!Resule=" + e.Result;
}
}
使用示例
//委托 这样调用就和直接调用函数没有区别
//这样做的本质就相当于将方法包装了下 然后直接调用
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);//错误的原因是进程间操作无效 使用一个简单的操作 就是关闭检测进程间的合法性
}
}
});