认识Thread对象的优先级
对于CPU时间线程有不同的优行级,这非常像应用程序获得CPU时间片一样。一个线程的Priority(优先级)属性控制它宿主进程时间的多大一个时间片被分配给它。这些优先级,从最大的到最小的排列顺序是:
记住这些设置控制了有限的进程CPU时间分配给线程的比例。因此,一个拥有最高级别优先级的线程可能不会留下多少时间给其它的线程完成它们的工作。一般来说,控制UI的线程应该给与正常的优先级,而那么没有UI元素的线程可以有一个更低的优先级,以便程序的UI仍然能够对用户做出响应。
控制线程行为
一旦你的新线程已经作好准备并开始运行,你可以通过它实例变量的方法对它实施一些控制。要知道你的线程现在在做些什么,你可以检查它的ThreadState属性。ThreadState返回一个枚举类型,它描述了线程的当前状态。
Sleep方法使一个线程“睡”上一段你指定的时间。在睡眠的时候,一个线程不会消耗CPU周期,所以睡眠是有效的使一个线程等待计算机资源来完成工作的方法。在睡眠的时候,线程的ThreadState属性会返回WaitSleepJoin.
如果你的线程睡了足够长的时间,你可以用Interrupt方法叫醒它。在一个线程在调用Interrupt方法会在这些线程运行的代码里抛出一个ThreadInterruptedException 异常。所以如果你的线程需要知道它是什么时候睡着的,只要设置Catch来捕捉这个异常就行了。
Interrupt 是一个非常违反直观的名字,因为你不能中断一个线程除非它一直是睡着的。如果在被中断的时候线程是醒着的,那么这个中断会被排队,直到下一次线程开始睡眠的时候,这时线程又被马上叫醒。这也许是你不想要的,所以在中断它以前先验证一个线程是不是处于WaitSleepJoin状态。
你还可以中断一个线程并在以后恢复它的运行。Suspend 方法挂起一个线程直到你调用Resume 方法。一个线程可能不会马上挂起它自己,在它真正挂起以前,它的ThreadState属性会是SuspendRequested.一旦它已经被挂起了,ThreadState属性就变成Suspended.那么Suspend/Resume
和 Sleep/Interrupt之间有什么不同呢?两个最大的不同在于:
要立即停止并除去一个线程,你应该调用Abort方法。Abort通过在它的代理里抛出一个ThreadAbortException异常来杀死线程。这是不可捕获的异常,但是如果它是在一个Try...Finally代码块里抛出的,那么Finally代码会在线程结束以前执行。
没有了Stop 方法
你可能看到有些书,文章,和文档说一种叫做Stop的方法是结束一个线程的另一方法。但这个方法已经在.Net Framework 的Beta 2版里被删除了。
下面是一个值得特别注意的方法。Join方法会挂起当前执行的线程直到引用的线程结束。例如,给出下面的C#代码:
//为Class1.DoSomething创建代理
ThreadStart delegate;
delegate = new ThreadStart(Class1.DoSomething);
Thread t = new Thread(del); //create new thread
t.Start();
t.Join();
Console.WriteLine("Hello There");
对Console.Writeline
的调用不会执行除非t已经完成了Class1.DoSomething的运行。你在一个线程不能在另一个线程完成工作以前完成自己的工作的时候使用Join方法,或者你在等待所有的线程结束运行以便你能够结束你的应用程序的时候使用它。一个等待Join其它线程的线程,它的ThreadState属性值是WaitSleepJoin.
那么应该在什么时候使用这些东西呢?
一个证明多线程是有用的很好的例子是当你运行一个很耗时的方法的时候,用户可能想要在它完成以前取消它。在只有一个线程的时候要取消它是困难的,但是只要再用一个线程,那么保持UI能够响应就是小菜一碟了。请看下面VB.NET代码的例子。
'Copyright 2001, TechRepublic
'Author: Lamont Adams
'Demonstrates basic free-threading techniques using
'.NET's thread object by creating a cancel dialog
'that can be used to cancel a simulated long-running
'operation on another thread.
'DISCLAIMER:
'Although every effort has been made to ensure that
'the code is safe and will run as intended,
'TechRepublic and CNet Networks cannot be held
'responsible for any damages resulting from
'the execution of this code. Use at your own risk.
Imports System.Threading
Public Class Form1
'The "main" form
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call
is required by the Windows Form Designer.
InitializeComponent()
'Add any
initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not
(components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
Friend WithEvents Button6 As System.Windows.Forms.Button
'Required by the Windows Form Designer
Private components As System.ComponentModel.Container
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.ListBox1 = New
System.Windows.Forms.ListBox()
Me.Button6 = New
System.Windows.Forms.Button()
Me.SuspendLayout()
'
'ListBox1
'
Me.ListBox1.Location = New
System.Drawing.Point(8, 8)
Me.ListBox1.Name = "ListBox1"
Me.ListBox1.Size = New
System.Drawing.Size(208, 121)
Me.ListBox1.TabIndex = 1
'
'Button6
'
Me.Button6.Location = New
System.Drawing.Point(8, 136)
Me.Button6.Name = "Button6"
Me.Button6.Size = New
System.Drawing.Size(200, 32)
Me.Button6.TabIndex = 6
Me.Button6.Text = "start long
operation"
'
'Form1
'
Me.AutoScaleBaseSize = New
System.Drawing.Size(5, 13)
Me.ClientSize = New
System.Drawing.Size(224, 173)
Me.Controls.AddRange(New
System.Windows.Forms.Control() )
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button6.Click
Dim t As Thread
'Create a new thread that does some
processing
t = New Thread(AddressOf Me.Count)
'Hand off that thread to the cancel
dialog
Dim f As New Form2(t)
'make this a background thread so
it doesn't interfere with the UI
t.Priority =
ThreadPriority.BelowNormal
'start the thread
t.Start()
'show the cancel dialog
f.ShowDialog()
'wait for the other thread to end.
t.Join()
End Sub
Private Sub Count()
'This routine will run in a tight
loop, and
'will never end to simulate a long,
processor
'intensive task that might need to
be cancelled.
'By running it on another thread,
we can keep
'the UI responsive.
Dim i As Long
Try
While True
i = i + 1
SyncLock ListBox1
Me.ListBox1.Items.Insert(0, CStr(i))
End SyncLock
If i > 320000 Then
i = 0
End If
End While
Catch
'pass
End Try
End Sub
End Class
Public Class Form2
'The cancellation dialog.
Inherits System.Windows.Forms.Form
Private mt As
System.Threading.Thread
#Region " Windows Form Designer generated code "
Public Sub New(ByVal t As
System.Threading.Thread)
MyBase.New()
'This call
is required by the Windows Form Designer.
InitializeComponent()
'Add any
initialization after the InitializeComponent() call
mt = t
End Sub
'Form overrides dispose to clean up
the component list.
Protected Overloads Overrides Sub
Dispose(ByVal disposing As Boolean)
If
disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
Friend WithEvents btnCancel As
System.Windows.Forms.Button
'Required by the Windows Form
Designer
Private components As
System.ComponentModel.Container
'NOTE: The following procedure is
required by the Windows Form Designer
'It can be modified using the
Windows Form Designer.
'Do not modify it using the code
editor.
<System.Diagnostics.DebuggerStepThrough()>
Private Sub InitializeComponent()
Me.btnCancel = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'btnCancel
'
Me.btnCancel.Location = New System.Drawing.Point(48, 24)
Me.btnCancel.Name = "btnCancel"
Me.btnCancel.Size = New System.Drawing.Size(168, 23)
Me.btnCancel.TabIndex = 0
Me.btnCancel.Text = "Click me to cancel"
'
'Form2
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(264, 93)
Me.Controls.AddRange(New System.Windows.Forms.Control() )
Me.Name =
"Form2"
Me.Text =
"Form2"
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnCancel.Click
'stop processing until we confirm
that user wants to cancel
mt.Suspend()
If MsgBox("Really cancel?",
MsgBoxStyle.YesNo, "Are you positively sure?") = MsgBoxResult.Yes Then
'the user
means it. kill the thread.
mt.Resume()
mt.Abort()
Else
'resume the
paused thread
mt.Resume()
End If
Me.Close()
End Sub
End Class
在多个用户需要同时访问同样的对象的时候,产生多个线程来处理这些请求能够提高程序的性能。与数据库,消息队列,文件系统,或者其它程序进行异步通讯的应用程序也能从多线程中得到好处。
不利的一面
你是说必须使用catch语句么?说对了。但实际上还不止这些讨厌的地方:
记住在创建和切换线程的时候要消耗一定的CPU时间。因为一个进程的CPU时间是有限的,所以许多同时执行的线程可能导致这个程序比一个单线程的完成同样工作的程序要慢。因为线程对同样的地址空间都有访问权,它们有可能在同一时间访问全局变量。这可能导致无法预见的后果,在你的程序中产生无法隔离的错误。
解决第一个问题的方法包括仔细的计划和审查来确保最优的性能。对于第二个问题的解决引入了一个叫同步的主题,或者说阻止多个线程同时访问相同的资源。这会是下一篇文章的主题。
现在你应该知道如何实验.Net对线程的支持了。你只需要注意不要用剪刀。(线程在英文中和线是同一个词)。