目录
关系梳理
- C# 中线程的使用一般使用以下三种类型
- Thread对象
- 函数委托
- Task对象(暂时没有研究)
- C# 中不管使用哪种方式使用线程,都一定会使用到委托
- C# 中在线程中使用委托,是一种较为安全、通用的跨线程修改UI方式,
- C# 中跨线程UI修改的使用形式与子线程的创建方式无关。
Thread 对象创建线程
优点
- 代码结构简单,使用比较方便
- 修改UI的逻辑结构比较简单
缺点
- 向子线程传递参数困难
- 从子线程获取结果值困难
适用情况
- 不需要和主线程进行数据传递的情况
- 子线程结果直接显示在UI控件中的情况
示例代码1:单纯的后台子线程处理(控制台程序)
public static void main()
{
//创建线程启动器(实际可以理解成一个限定,返回类型为void的广义委托)
ThreadStart start = new ThreadStart(ConsoleInformation);
//创建线程对象
Thread thread = new Thread(start);
//启动线程
thread.Start();
}
//子线程中要执行的耗时操作
public static void ConsoleInformation()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(string.Format("this is {0} time run in Thread A",i));
Thread.Sleep(500);
}
}
实例代码2:需要修改UI的子线程处理(WinFrom程序)
/// <summary>
/// 利用Thread对象创建线程并执行
/// </summary>
/// <remarks>该中方式不能传递参数进入线程,也没有办法指定线程完成后的事件回调或者获取线程结果</remarks>
private void Base_btn_start_Click(object sender, EventArgs e)
{
//UI准备,是一个ListView型控件
UI_lv_content.Items.Clear();
//创建线程启动器(实际可以理解成一个限定,返回类型为void的广义委托)
ThreadStart start = new ThreadStart(UpdateListViewContent);
//创建线程对象
Thread thread = new Thread(start);
//启动线程
thread.Start();
}
//子线程具体要执行的耗时操作,因为操作中要修改UI,因此修改UI的操作必须由其他委托执行
private void UpdateListViewContent()
{
for (int i = 0; i < 5; i++)
{
//非异步洗发没有错,但异步就不能执行,否则需要修改夸线程修改标识
//this.UI_lv_content.Items.Add(string.Format("this is {0} time in Thread",i));
//取消或线程UI访问检查(非常不推荐!)
//Control.CheckForIllegalCrossThreadCalls = false;
//异步操作时,使用委托执行 , 判定是否控件在主线程外
if (UI_lv_content.InvokeRequired)
UI_lv_content.Invoke(new Del_UpdateListViewContentParam(Invoke_UpdateListViewContent), i);
else
Invoke_UpdateListViewContent();
}
}
//真正执行UI修改的功能函数的委托申明(带一个参数)
private delegate void Del_UpdateListViewContentParam(int i);
//真正执行UI修改的功能函数
private void Invoke_UpdateListViewContent(int i)
{
this.UI_lv_content.Items.Add(string.Format("this is {0} time in Thread param", i));
this.Update();
//模拟耗时操作
Thread.Sleep(500);
}
实例代码3:通过对象参数传递并获取子线程结果
/// <summary>
/// 采用对象作为委托函数的外壳,实现Thread方式的线程的参数传递
/// </summary>
/// <remarks>该种方式有点类似于Java中继承Runnable的接口的做法,
/// 虽然该方法没有直接获取返回值,但可以通过强制监听Thread状态的方式处理线程完成的响应状态</remarks>
private void UI_btn_start4_Click(object sender, EventArgs e)
{
CustomParam param=new CustomParam();
param.X = 12;
param.Y = 24;
ThreadStart start = new ThreadStart(param.RunInThread);
Thread thread = new Thread(start);
thread.Start();
//如果线程还在运行,则同步执行主线程操作,防止UI拥塞
while (thread.IsAlive)
Application.DoEvents();
//若线程完成操作了,则通过传递的类对象获取处理结果
UI_lv_content.Items.Clear();
UI_lv_content.Items.Add("finished : " + param.Coordinate);
UI_lv_content.Update();
}
//不带参数的委托(委托A)
private delegate void Del_UpdateListViewContent();
参数类申明
public class CustomParam
{
private int m_X;
private int m_Y;
private string m_coordinate;
//和delegate void Del_UpdateListViewContent()统一类型的方法
public void RunInThread()
{
m_coordinate = string.Format("({0},{1})", X, Y);
Thread.Sleep(1000);
}
//getter和setter省略
}
但若采用该种方式修改UI,则会将UI控件传递到参数类中,从而破坏结构化程序设计
函数委托 创建线程
优点
- 线程操作权限比较大,可以明确的知道线程的运行状态
- 子线程的参数传递和参数获取比较方便
缺点
- 对于修改UI的情况,会出现委托的嵌套,增加代码的复杂程度
使用情况
- 需要在子线程内完成复杂计算,即需要向子线程提供复杂参数的情况
- 线程结果不在UI展示,而是被主线程其他地方使用的情况
示例代码1:带参数和结果值的后台子线程处理
/// <summary>
/// 直接使用委托开启子线程
/// </summary>
/// <remarks>该种方式没有显示创建Thread的过程,子线程由委托自身维护。
/// 改方式最大好处在于可以很方便的传递参数到线程中,同时可以获取线程的函数的处理结果
/// 但如果要处理修改UI的操作情况,则会出现委托中调用委托的操作,业务逻辑的复杂性
/// </remarks>
private void UI_btn_start3_Click(object sender, EventArgs e)
{
//UI准备
UI_lv_content.Items.Clear();
//依据UI判断如何处理主线程和子线程拥塞
is_Wait4Main = UI_ck_IsWaitMain2.Checked;
//直接创建委托-委托内容不修改UI
//Del_UpdateListViewContentAsyn del = new Del_UpdateListViewContentAsyn(Invoke_UpdateListViewContentAsyn);
//直接创建委托-委托内容修改UI;
Del_UpdateListViewContentAsyn del = new Del_UpdateListViewContentAsyn(Invoke_UpdateListViewContentAsynUI);
//执行委托,并依据委托类型传递参数
IAsyncResult iresult = del.BeginInvoke(10,12.1, null, null);
while (!iresult.IsCompleted)
Application.DoEvents();//该方式默认要处理UI拥塞,否则会出现UI锁死现象!
//获取委托结果
int result = del.EndInvoke(iresult);
//UI结果展示
//UI_lv_content.Items.Clear();
UI_lv_content.Items.Add("finished : " + result);
UI_lv_content.Update();
}
//带两个参数且觉有返回值的委托(委托C)
private delegate int Del_UpdateListViewContentAsyn(int max,double min);
//委托具体要执行的函数-不修改UI
private int Invoke_UpdateListViewContentAsyn(int max,double min)
{
Thread.Sleep(2000);
return new Random().Next(20);
}
为了在子线程运行过程中,不造成主线程拥塞,一定要对主线程拥塞进行处理
实例代码2:需要修改UI的子线程处理
//委托具体要执行的函数-不修改UI
private int Invoke_UpdateListViewContentAsynUI(int max, double value)
{
//为委托内容中由以委托方式调用修改UI的委托-委托嵌套
for (int i = 0; i < 5; i++)
{
if (UI_lv_content.InvokeRequired)//委托具体执行方法中又再次调用了委托(具体修改执行过程见Thread对象,实例代码2)
UI_lv_content.Invoke(new Del_UpdateListViewContentParam(Invoke_UpdateListViewContent), i);
else
Invoke_UpdateListViewContent();
}
Thread.Sleep(2000);
return new Random().Next(20);
}
其他注意事项
主线程拥塞处理
Thread对象的主线拥塞处理
Thread thread = new Thread(start);
thread.Start();
while (thread.IsAlive)
Application.DoEvents();
函数委托的拥塞处理
IAsyncResult iresult = del.BeginInvoke(10,12.1, null, null);
while (!iresult.IsCompleted)
Application.DoEvents();
一种简单化的后台处理模块 BackgroundWorker
说明
- 没有显式的线程创建过程
- 没有显式的UI修改委托创建过程
- 可以明确的监听到完成后台处理中和处理完成的操作
- 没有显式的主线程拥塞维护工作
该对象使用非常类似于Android平台下AsynTask类
实例代码
- 要在界面中拖拽一个非UI控件:BackgroundWorker
- 或者直接代码创建,不过这种方式需要为BackgroundWorker手工注册监听事件
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
具体的使用代码
private void UI_btn_start5_Click(object sender, EventArgs e)
{
//申明backgroundWorker1要上报进度报告
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
//UI准备
UI_lv_content.Items.Clear();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//执行耗时操作
for (int i = 1; i < 11; i++)
{
Thread.Sleep(1000);
//发送修改UI请求
backgroundWorker1.ReportProgress(i,""+i);
}
//记录结果
e.Result = new Random().Next(25);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//处理子线程运行过程中,发出的进度请求,这里是可以修改UI的
UI_lv_content.Items.Add(String.Format("doing... {0}/10, value : {1}",e.ProgressPercentage,e.UserState.ToString()));
UI_lv_content.Update();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//获得子线程的处理结果
UI_lv_content.Items.Add("finish:"+e.Result);
}