很多时候写windows程序都需要结合多线程,在.net中用如下得代码来创建并启动一个新的线程。
public void ThreadProc();
Thread thread = new Thread(new ThreadStart(ThreadProc));
thread.IsBackground = true;
thread.Start();
但是很多时候,在新的线程中,我们需要与UI进行交互,在.net中不允许我们直接这样做。可以参考MSDN中的描述:
“Windows 窗体”使用单线程单元 (STA) 模型,因为“Windows 窗体”基于本机 Win32 窗口,而 Win32 窗口从本质上而言是单元线程。STA 模型意味着可以在任何线程上创建窗口,但窗口一旦创建后就不能切换线程,并且对它的所有函数调用都必须在其创建线程上发生。除了 Windows 窗体之外,.NET Framework 中的类使用自由线程模型。
STA 模型要求需从控件的非创建线程调用的控件上的任何方法必须被封送到(在其上执行)该控件的创建线程。基类 Control 为此目的提供了若干方法(Invoke、BeginInvoke 和 EndInvoke)。Invoke 生成同步方法调用;BeginInvoke 生成异步方法调用。
Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。
正如所看到的,我们必须调用Invoke方法,而BeginInvoke可以认为是Invoke的异步版本。调用方法如下:
public delegate void OutDelegate(string text);
public void OutText(string text)
{
txt.AppendText(text);
txt.AppendText( "\t\n" );
}
OutDelegate outdelegate = new OutDelegate( OutText );
this.BeginInvoke(outdelegate, new object[]{text});
如果我们需要在另外一个线程里面对UI进行操作,我们需要一个类似OutText的函数,还需要一个该函数的委托delegate,当然,这里展示的是自定义的,.net中还有很多其他类型的委托,可以直接使用,不需要而外声明。例如:MethodInvoker和EventHandler,这两种类型委托的函数外观是固定的,MethodInvoker是void Function()类型的委托,而EventHandler是void Function(object, EventArgs)类型的委托,第一个不支持参数,第二中的参数类型和数量都是固定的,这两种委托可以很方便的调用,但是缺乏灵活性。请注意BeginInvoke前面的对象是this,也就是主线程。现在再介绍Control.InvokeRequired,Control是所有控件的基类,对于这个属性MSDN的描述是:
获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。
该属性可用于确定是否必须调用 Invoke 方法,当不知道什么线程拥有控件时这很有用。
也就是说通过判断InvokeRequired可以知道是否需要用委托来调用当前控件的一些方法,如此可以把OutText函数修改一下:
public delegate void OutDelegate(string text);
public void OutText(string text)
{
if( txt.InvokeRequired )
{
OutDelegate outdelegate = new OutDelegate( OutText );
this.BeginInvoke(outdelegate, new object[]{text});
return;
}
txt.AppendText(text);
txt.AppendText( "\t\n" );
}
注意,这里的函数没有返回,如果有返回,需要调用Invoke或者EndInvoke来获得返回的结果,不要因为包装而丢失了返回值。如果调用没有完成,Invoke和EndInvoke都将会引起阻塞。
现在如果我有一个线程函数如下:
public void ThreadProc()
{
for(int i = 0; i < 5; i++)
{
OutText( i.ToString() );
Thread.Sleep(1000);
}
}
如果循环的次数很大,或者漏了Thread.Sleep(1000);,那么你的UI肯定会停止响应,想知道原因吗?看看BeginInvoke前面的对象,没错,就是this,也就是主线程,当你的主线程不停的调用OutText的时候,UI当然会停止响应。
与以前VC中创建一个新的线程需要调用AfxBeginThread函数,该函数中第一个参数就是线程函数的地址,而第二个参数是一个类型为LPVOID的指针类型,这个参数将传递给线程函数。现在我们没有办法再使用这种方法来传递参数了。我们需要将传递给线程的参数和线程函数包装成一个单独的类,然后在这个类的构造函数中初始化该线程所需的参数,然后再将该实例的线程函数传递给Thread类的构造函数。代码大致如下:
public class ProcClass
{
private string procParameter = "";
public ProcClass(string parameter)
{
procParameter = parameter;
}
public void ThreadProc()
{
}
}
ProcClass threadProc = new ProcClass("use thread class");
Thread thread = new Thread( new ThreadStart( threadProc.ThreadProc ) );
thread.IsBackground = true;
thread.Start();
就是这样,需要建立一个中间类来传递线程所需的参数。
那么如果我的线程又需要参数,又需要和UI进行交互的时候该怎么办呢?可以修改一下代码:
public class ProcClass
{
private string procParameter = "";
private Form1.OutDelegate delg = null;
public ProcClass(string parameter, Form1.OutDelegate delg)
{
procParameter = parameter;
this.delg = delg;
}
public void ThreadProc()
{
delg.BeginInvoke("use ProcClass.ThreadProc()", null, null);
}
}
ProcClass threadProc = new ProcClass("use thread class", new OutDelegate(OutText));
Thread thread = new Thread( new ThreadStart( threadProc.ThreadProc ) );
thread.IsBackground = true;
thread.Start();
//-----------------------以下为自己写的异步委托的例子------------------------
namespace PhsControlDelegate
{
public partial class Form2 : Form
{
PhsControl.phsControl phsObj = new PhsControl.phsControl();
public delegate void OperationDelegate(string strMessage);
public Form2()
{
phsObj.OnJieShouDuanXin +=new PhsControl.JieShouDXHandler(phsObj_OnJieShouDuanXin);
}
public void SetText(string strMessage)
{
txtReceive.AppendText(strMessage);
}
protected void phsObj_OnJieShouDuanXin(Object sender, PhsControl.jsSmsArgs e)
{
OperationDelegate optionDelgate = new OperationDelegate(SetText);
this.BeginInvoke(optionDelgate, new object[] { e.dhHaoMa + " " + e.dxNeiRong + "\t\n" });
}
}
}
---------------------
使用委托的形式,调用线程,,,
using System;
using System.Threading;
namespace _012_线程
{
class Program
{
static void Main(string[] args) //在mian中线程是执行一个线程里面的语句的执行,是从上到下的
{
//通过委托 开启一个线程
//==============可用泛型传参数(无返回值)==============
Action threaA = ThreadTestA;
threaA.BeginInvoke(null,null); //开启一个新的线程去执行,threaA所引用的方法
Action<int> threaB = ThreadTestB;
threaB.BeginInvoke(111,null, null);
//可以认为线程是同时执行的 (异步执行)
Console.WriteLine("异步执行");
//================带返回值的形式====================
//第一种方式 检测线程结束 ----- IsCompleted线程是否行完毕
//Func<int, int> threaC = ThreadTestC;
接收异步线程返回值
//IAsyncResult returnResult = threaC.BeginInvoke(111, null, null);
//while (!res.IsCompleted)
//{
// Console.Write(".");
// Thread.Sleep(10); //控制子线程的检测频率,(每10ms检测一次)
//}
取得异步线程返回值
//int result = threaC.EndInvoke(res);
//Console.WriteLine("IsCompleted方式检测:" + result);
//第二种方式 检测线程结束 ----- 1000ms没结束就返回false,反之
Func<int, int> threaC = ThreadTestC;
//接收异步线程返回值
IAsyncResult returnResult = threaC.BeginInvoke(111, null, null);
bool isEnd = returnResult.AsyncWaitHandle.WaitOne(1000);
int result = 0;
if (isEnd)
{
result = threaC.EndInvoke(returnResult);
}
Console.WriteLine("EndInvoke()方式检测:" + isEnd +" "+ result);
//第三种方式 检测线程结束 ----- 通过回调,检测线程结束
Func<int,string, string> threaD = ThreadTestD;
//倒数第二个参数,表示委托类型的参数,(回调函数)当线程结束的时候会调用这个委托指向的方法
//最后一个参数,用来给回调函数传递数据
IAsyncResult asy = threaD.BeginInvoke(111,"Czhenya", OnCallKey, threaD);
//改为Lamdba表达式
threaD.BeginInvoke(111, "Czhenya",(ar)=>{
string res = threaD.EndInvoke(ar);
Console.WriteLine("在Lamdba表达式中取得:"+res);
},null);
Console.ReadKey();
}
static void OnCallKey(IAsyncResult ar)
{
Func<int, string, string> thread = ar.AsyncState as Func<int, string, string>;
string res = thread.EndInvoke(ar);
Console.WriteLine("在回调函数中取到的结果 :"+res);
}
/// <summary>
/// 一般是比较耗时的操作方法
/// </summary>
static void ThreadTestA()
{
Console.WriteLine("ThreaTestA");
}
static void ThreadTestB(int num)
{
Console.WriteLine("ThreaTestB "+num);
}
static int ThreadTestC(int num)
{
Console.WriteLine("ThreaTestC");
Thread.Sleep(100); //让当前线程休眠(暂停线程(参数单位:ms))
return num;
}
static string ThreadTestD(int num,string str)
{
Console.WriteLine("ThreaTestD");
return num +" "+ str;
}
}
}
运行结果图: