C# 中线程和委托

目录

    • 关系梳理
    • Thread 对象创建线程
      • 优点
      • 缺点
      • 适用情况
      • 示例代码1单纯的后台子线程处理控制台程序
      • 实例代码2需要修改UI的子线程处理WinFrom程序
      • 实例代码3通过对象参数传递并获取子线程结果
    • 函数委托 创建线程
      • 优点
      • 缺点
      • 使用情况
      • 示例代码1带参数和结果值的后台子线程处理
      • 实例代码2需要修改UI的子线程处理
    • 其他注意事项
      • 主线程拥塞处理
        • Thread对象的主线拥塞处理
        • 函数委托的拥塞处理
    • 一种简单化的后台处理模块 BackgroundWorker
      • 说明
      • 实例代码

关系梳理

  1. C# 中线程的使用一般使用以下三种类型
    1. Thread对象
    2. 函数委托
    3. Task对象(暂时没有研究)
  2. C# 中不管使用哪种方式使用线程,都一定会使用到委托
  3. C# 中在线程中使用委托,是一种较为安全、通用的跨线程修改UI方式,
  4. C# 中跨线程UI修改的使用形式与子线程的创建方式无关。

Thread 对象创建线程

优点

  1. 代码结构简单,使用比较方便
  2. 修改UI的逻辑结构比较简单

缺点

  1. 向子线程传递参数困难
  2. 从子线程获取结果值困难

适用情况

  1. 不需要和主线程进行数据传递的情况
  2. 子线程结果直接显示在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程序)

/// 
/// 利用Thread对象创建线程并执行
/// 
/// 该中方式不能传递参数进入线程,也没有办法指定线程完成后的事件回调或者获取线程结果
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:通过对象参数传递并获取子线程结果

/// 
/// 采用对象作为委托函数的外壳,实现Thread方式的线程的参数传递
/// 
/// 该种方式有点类似于Java中继承Runnable的接口的做法,
/// 虽然该方法没有直接获取返回值,但可以通过强制监听Thread状态的方式处理线程完成的响应状态
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控件传递到参数类中,从而破坏结构化程序设计

函数委托 创建线程

优点

  1. 线程操作权限比较大,可以明确的知道线程的运行状态
  2. 子线程的参数传递和参数获取比较方便

缺点

  1. 对于修改UI的情况,会出现委托的嵌套,增加代码的复杂程度

使用情况

  1. 需要在子线程内完成复杂计算,即需要向子线程提供复杂参数的情况
  2. 线程结果不在UI展示,而是被主线程其他地方使用的情况

示例代码1:带参数和结果值的后台子线程处理

/// 
/// 直接使用委托开启子线程
/// 
/// 该种方式没有显示创建Thread的过程,子线程由委托自身维护。
/// 改方式最大好处在于可以很方便的传递参数到线程中,同时可以获取线程的函数的处理结果
/// 但如果要处理修改UI的操作情况,则会出现委托中调用委托的操作,业务逻辑的复杂性
/// 
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

说明

  1. 没有显式的线程创建过程
  2. 没有显式的UI修改委托创建过程
  3. 可以明确的监听到完成后台处理中和处理完成的操作
  4. 没有显式的主线程拥塞维护工作

该对象使用非常类似于Android平台下AsynTask类

实例代码

  1. 要在界面中拖拽一个非UI控件:BackgroundWorker
  2. 或者直接代码创建,不过这种方式需要为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);
}

你可能感兴趣的:(C# 中线程和委托)