CSDN博客

img Paul_Ni

在.NET中的线程处理(6)

发表于2002/4/22 9:38:00  2635人阅读

使用线程和线程处理

创建、管理和销毁托管线程是非常容易的,但如果不了解托管线程和非托管线程之间的关系以及 ThreadAbortException,则这样做可能会导致预料不到的副作用。

创建线程  [C#]

在创建操作系统进程时,操作系统将插入一个线程以执行该进程(包括任何原始应用程序域)中的代码。从此刻起,就可以创建和销毁应用程序域,而不必创建或销毁任何操作系统线程。如果正在执行的代码是托管代码,则可以通过在线程类 Thread.CurrentThread 上检索静态属性来获取正在当前应用程序域中执行的线程的 Thread 对象。

创建 Thread 对象的新实例时,将创建新的托管线程。Thread 的构造函数采用 ThreadStart 委托作为其唯一参数,该委托用于包装在调用 Thread.Start 时由新的 Thread 调用的方法。多次调用 Thread.Start 将引发 ThreadStateException。

Thread.Start 向系统提交异步请求,并且该调用可能在新的线程实际启动之前立即返回。可以使用 Thread.ThreadState 和 Thread.IsAlive 在任一时刻确定线程的状态。Thread.Abort 中止线程,并对其进行标记以进行垃圾回收。下面的代码示例创建两个新线程以调用另一个对象上的实例和静态方法。

[Visual Basic]
Imports System
Imports System.Threading
 
Public Class ServerClass
   
   
   ' The method that will be called when the thread is started.
   Public Sub InstanceMethod()
      Console.WriteLine("ServerClass.InstanceMethod is running on another thread.")
      Thread.Sleep(3000) ' Pause for a moment to provide a delay to make threads more apparent.
      Console.WriteLine("The instance method called by the worker thread has ended.")
   End Sub 'InstanceMethod
   
   
   Public Shared Sub StaticMethod()
      
      Console.WriteLine("ServerClass.StaticMethod is running on another thread.")
      Thread.Sleep(5000) ' Pause for a moment to provide a delay to make threads more apparent.
      Console.WriteLine("The static method called by the worker thread has ended.")
   End Sub 'StaticMethod
End Class 'ServerClass
 
Public Class Simple
   
   Public Shared Sub Main() 
      Console.WriteLine("Thread Simple Sample")
      
      Dim serverObject As New ServerClass()
      
      ' Create the thread object, passing in the serverObject.InstanceMethod method
      ' using a ThreadStart delegate.
      Dim InstanceCaller As New Thread(New ThreadStart(AddressOf serverObject.InstanceMethod))
      
      ' Start the thread.
      InstanceCaller.Start()
      
      Console.WriteLine("The Main() thread calls this after starting the new InstanceCaller thread.")
      
      ' Create the thread object, passing in the serverObject.StaticMethod method
      ' using a ThreadStart delegate.
      Dim StaticCaller As New Thread(New ThreadStart(AddressOf ServerClass.StaticMethod))
      
      ' Start the thread.
      StaticCaller.Start()
      
      Console.WriteLine("The Main() thread calls this after starting the new StaticCaller threads.")
      
   End Sub 'Main
End Class 'Simple
[C#]
using System;
using System.Threading;
 
public class ServerClass{
 
   // The method that will be called when the thread is started.
   public void InstanceMethod(){
      Console.WriteLine("ServerClass.InstanceMethod is running on another thread.");
      // Pause for a moment to provide a delay to make threads more apparent.
      Thread.Sleep(3000);
      Console.WriteLine("The instance method called by the worker thread has ended.");
   }
 
   public static void StaticMethod(){
 
      Console.WriteLine("ServerClass.StaticMethod is running on another thread.");
      // Pause for a moment to provide a delay to make threads more apparent.
      Thread.Sleep(5000);
      Console.WriteLine("The static method called by the worker thread has ended.");
   }
 
}
 
public class Simple{
 
   public static int Main(String[] args){
 
      Console.WriteLine("Thread Simple Sample");
 
      ServerClass serverObject = new ServerClass();
 
      // Create the thread object, passing in the serverObject.InstanceMethod method
      // using a ThreadStart delegate.
      Thread InstanceCaller = new Thread(new ThreadStart(serverObject.InstanceMethod));
 
      // Start the thread.
      InstanceCaller.Start();
 
      Console.WriteLine("The Main() thread calls this after starting the new InstanceCaller thread.");
 
      // Create the thread object, passing in the serverObject.StaticMethod method
      // using a ThreadStart delegate.
      Thread StaticCaller = new Thread(new ThreadStart(ServerClass.StaticMethod));
 
      // Start the thread.
      StaticCaller.Start();
 
      Console.WriteLine("The Main() thread calls this after starting the new StaticCaller threads.");
 
      return 0;
   }
}

重要的一点是,线程还能够调用带参数的方法,即使 ThreadStart 委托只带一个参数——表示状态的对象。正是该对象应将参数传送给被调用方法。下面的简单代码示例说明了一种使用状态对象来传递参数的方法。

[Visual Basic]
Imports System
Imports System.Threading
 
Public Class SimpleThread
   
   
   Delegate Sub Start(o As Object)
   
   Private Class Args
      
      Public o As Object
      Public s As Start
      
      Public Sub work()
         s(o)
      End Sub 'work
   End Class 'Args
   
   
   Public Shared Function CreateThread(s As Start, arg As Object) As Thread
      
      Dim a As New Args()
      a.o = arg
      a.s = s
      Dim t As New Thread(AddressOf a.work)
      Return t
   End Function 'CreateThread
End Class 'SimpleThread
 
Class Worker
   Public Shared Sub WorkerMethod(o As Object)
      Console.WriteLine("WorkerMethod: " + o.ToString())
   End Sub 'WorkerMethod
End Class 'Worker
 
Public Class Work
   Public Shared Sub Main()
      
      Dim t As Thread = SimpleThread.CreateThread(AddressOf Worker.WorkerMethod, 51)
      t.Start()
      t.Join(Timeout.Infinite)
   End Sub 'Main
End Class 'Work
[C#]
using System;
using System.Threading;
 
public class SimpleThread{
 
   public delegate void Start (object o);
 
   private class Args{
      public object o;
      public Start s;
      public void work(){ 
      s(o); 
   }
   }
 
   public static Thread CreateThread (Start s, Object arg){
 
      Args a = new Args();
      a.o = arg;
      a.s = s;
      Thread t = new Thread (new ThreadStart (a.work));
      return t;
   }
}
 
class Worker{
   public static void WorkerMethod(object o){
      Console.WriteLine ("WorkerMethod: " + o);
   }
}
 
public class Work{
   public static void Main(){
      Thread t = SimpleThread.CreateThread (new SimpleThread.Start(Worker.WorkerMethod), 51);
      t.Start ();
      t.Join (Timeout.Infinite);
   }
}

暂停和继续线程

在启动线程后,通常想将该线程暂停一段固定时间。调用 Thread.Sleep 将使当前线程立即阻塞一段时间(该时间是传递给 Sleep 的毫秒数),并将其时间片的剩余部分提供给另一个线程。一个线程不能对另一个线程调用 Sleep。调用 Thread.Sleep(Timeout.Infinite) 将使线程休眠,直到它被调用 Thread.Interrupt 的另一个线程中断或被 Thread.Abort 中止为止。

还可以通过调用 Thread.Suspend 来暂停一个线程。当线程对其自身调用 Thread.Suspend 时,该调用将阻塞,直到该线程被另一个线程继续为止。当一个线程对另一个线程调用 Thread.Suspend 时,该调用就成为使另一个线程暂停的非阻塞调用。调用 Thread.Resume 将使另一个线程跳出挂起状态并使该线程继续执行,而与调用 Thread.Suspend 的次数无关。例如,如果连续调用 Thread.Suspend 五次,然后调用 Thread.Resume,则该线程将在对 Resume 的调用后面立即继续执行。

Thread.Sleep 不同,Thread.Suspend 不会使线程立即停止执行。公共语言运行库必须一直等待,直到线程到达安全点之后它才可以将该线程挂起。如果线程尚未启动或已经停止,则它将不能挂起。

SuspendResume 方法并不是对所有应用程序都有用,并且不应将其与同步机制混淆。由于 SuspendResume 不依赖于受控制线程的协作,因此它们具有高度侵犯性并会导致严重的应用程序问题,如死锁(例如,如果挂起的线程占有另一个线程需要的资源,就会发生这种情况)。某些应用程序确实需要控制线程的优先级以提高性能。为了做到这一点,应该使用 Thread.Priority 而不是 Thread.Suspend

可以使用许多方式来阻塞线程。例如,可以通过调用 Thread.Join 使一个线程等待另一个线程停止。可以使用 Monitor.Wait 使一个线程等待访问一个同步对象,还可以使用 Thread.Sleep 使该线程休眠。可以通过对被阻塞的线程调用 Thread.Interrupt 以引发 ThreadInterruptedException 来中断正在等待的线程,从而使该线程跳出阻塞调用。线程应捕获 ThreadInterruptedException 并执行适当的操作以继续工作。如果线程忽略该异常,则运行库将捕获该异常并停止该线程。

如果等待是托管等待,则 Thread.InterruptThread.Abort 都将立即唤醒线程。如果等待是非托管等待(例如对 Win32 WaitForSingleObject 函数的平台调用调用),则 InterruptAbort 都不能得到对该线程的控制,直到它返回到或调用到托管代码中为止。在托管代码中:

  • Thread.Interrupt 将线程从它可能处于的任何等待中唤醒,并在目标线程中引发 ThreadInterruptedException
  • Thread.Abort 类似于 Thread.Interrupt,除了它引发 ThreadAbortException 以外。该异常是一种特殊的异常,它不能从托管代码中捕获(尽管 finally 块被执行)。

销毁线程  [C#]

Thread.Abort 方法用于永久地停止逻辑线程。当调用 Abort 时,公共语言运行库将引发 ThreadAbortException。线程可以捕获该异常,但不能取消它。运行库将执行 catch 块和任何 finally 块,并且如果该线程是一个托管线程,系统将悄悄地停止该线程而不会通知用户。

由于 Thread.Abort 不会使线程立即中止,因此如果需要确保线程被停止,则必须调用 Thread.Join 以在该线程上等待。Join 是一个阻塞调用,该调用直到线程实际停止执行后才会返回。一旦线程被中止,它将无法重新启动。

您还可以调用 Thread.Join 并传递超时时间段。如果线程在超时结束之前死亡,该调用将返回 true。否则,如果该时间在线程死亡之前过期,则该调用将返回 false。在对 Thread.Join 的调用上等待的线程可以被调用 Thread.Interrupt 的其他线程中断。

ThreadAbortException

ThreadAbortException 可以在托管代码中的任何位置发生。但是,它们仅在具有足够特权的线程调用 Thread.Abort 或 AppDomain.Unload(其本身使用 Thread.Abort)时才发生。处理它们的最佳方式是具有足够的 finallycatch 子句,以便在您必须中途退出时维护一致的状态。必要时,引起 ThreadAbortException 的特权代码将捕获并重置它。

下面的 C# 代码示例不正确。

[C#]
try {
   // Insert code that throws an exception.
} catch (Exception e) {
   // Insert the first part of backout code here.
   // < --- ThreadAbortException thrown by the system < -- 
}
...// additional backout code here.

若要使前面的代码工作,您必须将附加的退出代码移到 catch 块中或将其放在 finally 块中。这是因为在进入 catch 块后,将由系统隐式重新引发 ThreadAbortException,而不管您在该块中对其进行了何种处理。换句话说就是,虽然您可以捕获 ThreadAbortException,但您不能取消它——它将根据需要被自动重新引发。显式取消其中一个异常的唯一方式是调用 Thread.ResetAbort,而这要求具有适当的特权。只有当您首先引起 ThreadAbortException 时,您才应执行此操作。

调度线程

每个线程都具有分配给它的线程优先级。在公共语言运行库中创建的线程最初分配的优先级为 ThreadPriority.Normal。在运行库以外创建的线程保留它们在进入托管环境之前具有的优先级。您可以使用 Thread.Priority 属性获取或设置任何线程的优先级。

线程是根据其优先级而调度执行的。即使线程正在运行库中执行,所有线程都是由操作系统分配处理器时间片的。用于确定线程执行顺序的调度算法的详细情况随每个操作系统的不同而不同。在某些操作系统下,具有最高优先级(相对于可执行线程而言)的线程经过调度后总是首先运行。如果具有相同优先级的多个线程都可用,则计划程序将遍历处于该优先级的线程,并为每个线程提供一个固定的时间片来执行。只要具有较高优先级的线程可以运行,具有较低优先级的线程就不会执行。如果在给定的优先级上不再有可运行的线程,则计划程序将移到下一个较低的优先级并在该优先级上调度线程以执行。如果具有较高优先级的线程可以运行,则具有较低优先级的线程将被抢先,并允许具有较高优先级的线程再次执行。除此之外,当应用程序的用户界面在前台和后台之间移动时,操作系统还可以动态调整线程优先级。其他操作系统可以选择使用不同的调度算法。

总结

    上面是VS.NET中.NET线程调用方面的基本概念和示例代码,整理出来给大家参考一下。有任何建议请MAIL我 paulni@citiz.net

 

0 0

相关博文

我的热门文章

img
取 消
img