1 委托简介
C#允许用delegate关键字创建一种特殊的类,称为委托类。委托类的示例称为委托对象。
委托对象是一种指向一个或者多个方法(静态或者非静态的引用)。我们可以用调研方法的语法“调用”委托对象。这样调用委托对象所引用的方法。如果这些方法的调用是在调用委托的方法所在线程中完成,这种调用成为同步调用。
1.1 委托建立和引用
(1)先定义一个方法,例如:
int Max(int x,int y){
return x>y?x:y;
}
(2)建立一个委托类型myDelegate,并声明该委托可以指向的方法的签名(函数原型)
delegate void MyDelegate(int a,int b);
(3)建立一个委托类的实例md,并指向要调用的方法Max
//方法1:利用委托类的构造方法指定,这是最为常见的一种方式
MyDelegate md=new MyDelegate(Max);
//方法2:利用自动推断方式来指明要调用的方法,该形式更类型于函数指针
MyDelegate md=Max;
1.2 利用委托类实例调用所指向的方法
int c=md(4,5);
2 访问主线程控件
C#中,跨线程访问控件必须要借用委托完成。跨线程对UI控件进行操作是比较常见的一种操作。例如图1,在主进程form1中,点击“启动线程”后开启一个子进程,在子进程将右下直角textbox的内容周期性的添加到上面的listbox中。
图1 跨线程对UI控件进行操作
设计思路如下:(1)在Form1类中设置一个AddText(string text),负责将text添加到控件ltbViewText内容中,(2)如果是本线程调用AddText(),则直接对控件进行添加操作:ltbViewText.Items.Add(text),(3)如果是其他线程调用AddText(),必须必须通过控件的Invoke() 方法调用引用AddText的委托实例。
判断是否是本线程操作控件的方法是根据控件的InvokeRequired属性值来决定:如果该值为true则是外部线程操作,否则就是本线程操作。
AddText(string text)的实现代码如下:
private delegate void AddTextDelegate(string text);//声明一个委托类
public void AddText(string text){
if (ltbViewText.InvokeRequired) //不是创建控件的线程操作,则通过调用委托完成
{
AddTextDelegate td = AddText; //创建一个委托类实例md->AddText
ltbViewText.Invoke(td, text);//通过Invoke方法调用引用AddText的委托实例
}
Else //是,直接对控件进行操作——添加内容
ltbViewText.Items.Add(text);
}
实现对UI控件进行操作的主要流程图如下:
图2 跨线程对UI控件进行操作的主要流程图
实现代码(代码来源于文献【1】)如下:
(1)CTextOutput类,用于创建子线程
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AccessControlInThread
{
class CTextOutput
{
public volatile bool shouldstop = false;
public volatile string strOutputText;
private Form1 form1;
public CTextOutput(Form1 form1, string strText)
{
this.form1 = form1;
strOutputText = strText;
}
public void WriteText()
{
form1.AddText(strOutputText);
while (shouldstop == false)
{
Thread.Sleep(100); //输出线程休眠100毫秒
form1.AddText(strOutputText);
}
form1.AddText("自动填表线程终止");
}
}
}
(2)Form1类,主线程类
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace AccessControlInThread
{
public partial class Form1 : Form
{
Thread fillListThread;
CTextOutput ctoFillList;
public Form1()
{
InitializeComponent();
ctoFillList = new CTextOutput(this, "default");
String th = Thread.CurrentThread.ManagedThreadId.ToString();
MessageBox.Show("主线程是 " + th);
}
private delegate void AddTextDelegate(string text);
public void AddText(string text)
{
if (ltbViewText.InvokeRequired)
{
String th1 = Thread.CurrentThread.ManagedThreadId.ToString();
MessageBox.Show("true当前线程是:"+th1);
AddTextDelegate td = AddText;
ltbViewText.Invoke(td, text);
}
else
ltbViewText.Items.Add(text);
String th = Thread.CurrentThread.ManagedThreadId.ToString();
MessageBox.Show("else当前线程是 "+th);
}
private void btnStartThread_Click(object sender, EventArgs e)
{
ltbViewText.Items.Clear();
ctoFillList.shouldstop = false;
ctoFillList.strOutputText = tbText.Text;
fillListThread = new Thread(ctoFillList.WriteText);
fillListThread.IsBackground = true;
fillListThread.Start();
}
private void btnEndThread_Click(object sender, EventArgs e)
{
ctoFillList.shouldstop = true;
fillListThread.Join(0);
}
private void tbText_TextChanged(object sender, EventArgs e)
{
ctoFillList.strOutputText = tbText.Text;
}
}
}
参考文献
1、何波,傅由甲;C#网络程序编程(第二版);清华大学出版社,2019.
2、(34条消息) C# 委托 回调 操作UI主线程 学习笔记_SumC Ronnie的博客-CSDN博客
3、C#线程(异步委托) - 唧唧复唧唧木兰当户织 - 博客园 (cnblogs.com)