在多线程应用程序中提供和返回值是很复杂的,因为必须将对某个过程的引用传递给线程类的构造函数,该过程不带参数也不返回值。下面几节介绍一些提供参数和从不同线程上的过程返回值的简单方法。
为多线程过程提供参数
为多线程方法调用提供参数的最好办法是将目标方法包裹在类中,并为该类定义字段,这些字段将被用作新线程的参数。这种方法的优点是,任何时候想要启动新线程,都可以创建类的新实例,该实例带有自身的参数。例如,假设您有一个计算三角形面积的函数,如以下代码所示:
Function CalcArea(ByVal Base As Double, ByVal Height As Double) As Double CalcArea = 0.5 * Base * Height End Function
可以编写一个包含 CalcArea 函数的类,并创建一些字段来存储输入参数,如以下所示:
Class AreaClass Public Base As Double Public Height As Double Public Area As Double Sub CalcArea() Area = 0.5 * Base * Height MsgBox("The area is: " & Area) End Sub End Class
要使用 AreaClass,可以创建一个 AreaClass 对象并设置 Base 和 Height 属性,如以下代码所示:
Protected Sub TestArea() Dim AreaObject As New AreaClass Dim Thread As New System.Threading.Thread _ (AddressOf AreaObject.CalcArea) AreaObject.Base = 30 AreaObject.Height = 40 Thread.Start() End Sub
注意,调用 CalcArea 方法后,TestArea 过程并不检查 Area 字段的值。因为 CalcArea 运行于单独的线程,因此,如果在调用 Thread.Start 后立即检查,则无法确定 Area 字段已被设置。下一节讨论自多线程过程返回值的更好方法。
从多线程过程返回值
由于这些过程不能为函数也不能使用 ByRef 参数,因而从运行于不同线程的过程返回值是很复杂的。返回值的最简单方法是:使用 BackgroundWorker 组件来管理线程,在任务完成时引发事件,然后用事件处理程序处理结果。
下面的示例通过从运行于单独线程的某过程引发一个事件来返回值:
Private Class AreaClass2 Public Base As Double Public Height As Double Function CalcArea() As Double ' Calculate the area of a triangle. Return 0.5 * Base * Height End Function End Class Private WithEvents BackgroundWorker1 As New System.ComponentModel.BackgroundWorker Private Sub TestArea2() Dim AreaObject2 As New AreaClass2 AreaObject2.Base = 30 AreaObject2.Height = 40 ' Start the asynchronous operation. BackgroundWorker1.RunWorkerAsync(AreaObject2) End Sub ' This method runs on the background thread when it starts. Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles BackgroundWorker1.DoWork Dim AreaObject2 As AreaClass2 = CType(e.Argument, AreaClass2) ' Return the value through the Result property. e.Result = AreaObject2.CalcArea() End Sub ' This method runs on the main thread when the background thread finishes. Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, _ ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _ Handles BackgroundWorker1.RunWorkerCompleted ' Access the result through the Result property. Dim Area As Double = CDbl(e.Result) MsgBox("The area is: " & Area) End Sub
可以通过使用 QueueUserWorkItem 方法的可选 ByVal 状态对象变量为线程池线程提供参数和返回值。线程计时器线程也支持将状态对象用于此目的。有关线程池和线程计时器的信息,请参见线程池和线程计时器。
http://msdn2.microsoft.com/zh-cn/library/wkays279(VS.80).aspx
===========================================
关于线程的参数、“返回值”、及线程的中止
关于线程的参数(2.0)、“返回值”、及线程的中止
1.线程的参数:
有时候会想向辅助线程传递些
信息
,这里需要用到ParameterizedThreadStart 委托
示例:
private void btRunThread_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ParameterizedThreadStart(this.ThreadRun));
t.Start(100);
}
private void ThreadRun(object o)
{
this.lbCompleted.Invoke((MethodInvoker)delegate { this.lbCompleted.Text = System.Convert.ToString(o); });
}
2.通过 代理 可以大致实现类似功能,示例:
class Program
{
static void Main(string[] args)
{
ThreadClass tc = new ThreadClass(new MyDlg(DlgMethod));
Thread thread = new Thread(new ThreadStart(tc.ThreadRun));
Console.WriteLine("second thread start");
thread.Start();
thread.Join();
Console.WriteLine("second thread completed");
Console.Read();
}
private static void DlgMethod(int i)
{
Console.WriteLine("Second Thread Result:{0}", i);
}
}
public delegate void MyDlg(int i);
class ThreadClass
{
private MyDlg myDlg;
public ThreadClass(MyDlg pDlg)
{
this.myDlg = pDlg;
}
public void ThreadRun()
{
int total = 0;
for (int i = 0; i < 100; i++)
{
total += i;
}
if (myDlg != null)
{
myDlg(total);
}
}
}
3.线程的中止:
(1).join方法
MSDN注释:在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到某个线程终止为止。
看得一头雾,自己试了一下,似乎线程在调用join方法之后,该线程抢占了所有的cpu时间,直到线程的任务完成。不知道是这是这样?
(2).abort方法
立即中止线程
(3).定义标识量
示例:
class Program
{
private static bool stop;
static void Main(string[] args)
{
stop = false;
Thread t = new Thread(new ThreadStart(ThreadRun));
t.Start();
Thread.Sleep(100);
stop = true;
Console.Read();
}
static void ThreadRun()
{
while (!stop)
{
Console.WriteLine("Do Some Work...");
}
}
}
http://www.cnblogs.com/KissKnife/archive/2006/10/03/520463.html
=======================================
控制线程
在线程启动以后,可以通过调用线程对象的方法来控制线程的状态。可以通过调用Thread.Sleep方法来暂停一个线程的执行,这个方法可以接收一个整型值,用来决定线程休眠的时间。对于本文的实例程序,为了让列表项目增加的速度变慢,在其中放入了一个Sleep方法的调用。
可以通过调用Thread.Sleep(System.Threading.Timeout.Infinite)来让线程进入休眠状态,但是,这个调用的休眠时间是不确定的。要中断这个休眠,可以调用Thread.Interrupt方法。
通过调用Thread.Suspend方法可以挂起线程。挂起可以暂停一个线程,直到另一个线程调用Thread.Resume为止。休眠和挂起的区别是,挂起并不立刻让线程进入一个等待的状态,线程并不会挂起,直到.NET runtime认为现在已经是一个安全的地方来挂起它了,而休眠则会立刻让线程进入一个等待的状态。
表3、停止线程的执行 |
private void button2_Click (object sender, System.EventArgs e) { t1.Abort(); } |
Thread.Abort方法可以停止一个线程的执行。本文的实例程序通过加入一个按钮button2来停止后台处理,在事件处理程序中调用了Thread.Abort方法,如表3所示。
这就是多线程的强大之处。用户界面的响应很快,因为用户界面运行在一个单独的线程中,而后台的处理运行在另外一个线程中。在用户按下按钮button2时,就会马上得到响应,并且停止后台处理。
通过多线程程序传送数据
在实际工作中,还需要使用到多线程的许多复杂特性。其中一个问题就是如何将程序的数据由线程类的构造器传入或者传出。对于放到另外一个线程中的过程,既不能传参数给它,也不能由它返回值,因为传入到线程构造器的过程是不能拥有任何参数或者返回值的。为了解决这个问题,可以将过程封装到一个类中,这样,方法的参数就可使用类中的字段。
本文给出了一个简单的例子,计算一个数的平方。为了在一个新的线程中使用这个过程,将它封装到一个类中,如表4所示。
使用表5所示的代码在一个新的线程上启动CalcSquare过程。
表4、计算一个数的平方 | 表5、在一个新的线程上启动CalcSquare过程 | |
public class SquareClass { public double Value; public double Square; public void CalcSquare() { Square = Value * Value; } } | private void button1_Click(object sender, System.EventArgs e) { SquareClass oSquare =new SquareClass(); t2 = new Thread(new ThreadStart(oSquare.CalcSquare)); oSquare.Value = 30; t2.Start(); } |
在上述例子中,线程启动后,并没有检查类中的square值,因为即使调用了线程的start方法,也不能确保其中的方法马上执行完。要从另一个线程中得到需要的值,有几种方法,其中一种方法就是在线程完成的时候触发一个事件。表6所示的代码为SquareClass加入了事件声明。
表6、为SquareClass加入事件声明 |
public delegate void EventHandler(double sq); // 说明委派类型 public class SquareClass { public double Value; public double Square; public event EventHandler ThreadComplete; // 说明事件对象 public void CalcSquare() { Square = Value * Value; // 指定事件处理程序 ThreadComplete+=new EventHandler(SquareEventHandler); if( ThreadComplete!=null)ThreadComplete(Square); // 触发事件 } public static void SquareEventHandler(double Square ) // 定义事件处理程序 { MessageBox.Show(Square.ToString ()); } } |
对于这种方法,要注意的是事件处理程序SquareEventHandler运行在产生该事件的线程t2中,而不是运行在窗体执行的线程中。
同步线程
在线程的同步方面,C#提供了几种方法。在上述计算平方的例子中,需要与执行计算的线程同步,以便等待它执行完并且得到结果。另一个例子是,如果在其它线程中排序一个数组,那么在使用该数组前,必须等待该处理完成。为了实现同步,C#提供了lock声明和Thread.Join方法。
lock声明
表7、使用lock声明 |
public void CalcSquare1() { lock( typeof(SquareClass)) { Square = Value * Value; } } |
lock可以得到一个对象引用的唯一锁,使用时只要将该对象传送给lock就行了。通过这个唯一锁,可以确保多个线程不会访问共享的数据或者在多个线程上执行的代码。要得到一个锁,可以使用与每个类关联的System.Type对象。System.Type对象可以通过使用typeof运算得到,如表7所示。
Thread.Join方法
表8、使用Thread.Join方法 |
private void button1_Click(object sender, System.EventArgs e) { SquareClass oSquare =new SquareClass(); t2 = new Thread(new ThreadStart(oSquare.CalcSquare)); oSquare.Value = 30; t2.Start(); if( t2.Join (500) ) { MessageBox.Show(oSquare.Square.ToString ()); } } |
Thread.Join方法可以等待一个特定的时间,直到一个线程完成。如果该线程在指定的时间内完成了,Thread.Join将返回True,否则它返回False。在上述平方的例子中,如果不想使用触发事件的方法,可以调用Thread.Join的方法来确定计算是否完成了
http://blog.csdn.net/Awinye/archive/2005/11/15/530175.aspx
================================
传参通过构造,或属性来做,
返回value也可以属性SquareClass.Square
返回value的可以通过委托为好
public delegate void EventHandler(double sq); // 说明委派类型
public class SquareClass
{
public double Value;
public double Square;
public event EventHandler ThreadComplete; // 说明事件对象
public void CalcSquare() //其它线程执行
{
Square = Value * Value;
// 指定事件处理程序
if( ThreadComplete!=null)ThreadComplete(Square); // 触发事件
}
}
private double result;
private void button1_Click(object sender, System.EventArgs e)
{
SquareClass oSquare =new SquareClass();
t2 = new Thread(new ThreadStart(oSquare.CalcSquare));
oSquare.Value = 30;
t2.ThreadComplete+=new SquareClass.EventHandler(SquareEventHandler);
t2.Start();
}
public static void SquareEventHandler(double Square ) // 定义事件处理程序
{
result=Square
MessageBox.Show(Square.ToString ()); //返回
}