65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 线程参考手册

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (17投票s)

2010 年 5 月 7 日

CPOL

11分钟阅读

viewsIcon

44720

一篇定义了托管线程从基础到中级概念的文章。

摘要

线程是执行单元。Microsoft 从 Win32 和 NT 时代就开始使用线程,对其的理解是必不可少的。以我有限的知识,我整理了一份“手册”,可以作为例如基础线程和实际应用线程的参考。本节作为文章的一部分,仅作为对线程概念的基础介绍。有线程经验者可以忽略。要创建一个线程,您必须遵循以下步骤:

  1. 创建一个不接受参数且不返回任何数据的方法。
  2. 创建一个新的 ThreadStart 委托,并指定步骤 1 中创建的方法。
  3. 创建一个 Thread 对象,并指定步骤 2 中创建的 ThreadStart 对象。
  4. 调用 ThreadStart 以开始新线程的执行。代码看起来会是这样的
using System;
using System.Threading;
public class Program {
    public static void Main() {
        ThreadStart operation = new ThreadStart(SimpleWork);
        Thread thr = new Thread(operation);
        thr.Start();
    }
    private static void SimpleWork()
    {
        Console.WriteLine("Thread:  {0}", 
                Thread.CurrentThread.ManagedThreadId);
    }
}

以下是 Thread 类的一些属性

  • IsAlive:获取一个值,该值指示当前线程是否正在执行。
  • IsBackground:获取或设置线程是否作为后台线程运行。
  • IsThreadPoolId:获取此线程是否为线程池中的线程。
  • ManagedThreadId:获取用于标识当前线程的数字。
  • Name:获取或设置与线程关联的名称。
  • Priority:获取或设置线程的优先级。
  • ThreadState:获取线程的 ThreadState 值。

比上面示例更可能遇到的情况是您想要创建多个线程

using System;
using System.Threading;

public class Program {
    public static void Main() {
       ThreadStart operation = new ThreadStart(SimpleWork);
       for (int x = 1; x <= 5; ++x)
       {
           Thread thr = new Thread(operation);
           thr.Start();
       } 
    }
    private static void SimpleWork()
    {
        Console.WriteLine("Thread:  {0}", Thread.CurrentThread.ManagedThreadId);
    }
}

输出

Thread:  3
Thread:  4
Thread:  5
Thread:  6
Thread:  7

以下是 Thread 的一些方法(非静态!!)

  • Abort:在线程上引发 ThreadAbort 异常,指示线程应被中止。
  • Interrupt:当线程处于阻塞状态时,引发 ThreadInterruptException
  • Join:阻塞调用线程,直到该线程终止。
  • Start:设置一个线程以安排执行。

使用 Thread.Join 有时是必要的,因为大多数情况下,您需要应用程序等待线程完成执行。为了实现这一点,Thread 类支持 Join 方法,这是一个静态方法。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading;

class InterruptAwareWorker
{
    private bool interruptRequested;
    private Thread myThread;

    public void Interrupt()
    {
        if (myThread == null)
            throw new InvalidOperationException();
        interruptRequested = true;
        myThread.Interrupt();
        myThread.Join();
    }

    private void CheckInterrupt()
    {
        if (interruptRequested)
            throw new ThreadInterruptedException();
    }

    public void DoWork(object obj)
    {
        myThread = Thread.CurrentThread;
        try
        {
            while (true)
            {
                // Do some work… (including some blocking operations)
                CheckInterrupt();
                // Do some more work…
                CheckInterrupt();
                // And so forth…
            }
        }
                 
        catch (ThreadInterruptedException)
            {
                // Thread was interrupted; perform any cleanup.
                Console.WriteLine("Thread was interrupted...");
                return;
            }
        }
        
        public static void Main()
        {
            InterruptAwareWorker w = new InterruptAwareWorker();
            Thread t = new Thread(w.DoWork);
            t.Start();
            // Do some work…
            // Uh-oh, we need to interrupt the worker.
            w.Interrupt();
            t.Join();
        }
    }

输出

Thread was interrupted...

此代码作为 Visual Studio 2010 中的控制台应用程序运行,因此只能在 .NET 4.0 上运行,但在命令行上。为了理解概念,这里有一个连接线程的示例

using System;
using System.Threading;
public class Program {  
    public static void Main()
    {
       int threadCount = 5;
       Thread[] threads = new Thread[threadCount];

       for (int i = 0; i < threadCount; i++)
       {
           int idx = i;
           threads[i] = new Thread(delegate() { Console.WriteLine("Worker {0}", idx); });
       }

       // Now begin execution of each thread using the delegate keyword
       Console.WriteLine("Beginning thread execution...");
       Array.ForEach(threads, delegate(Thread t) { t.Start(); });

       // And lastly join on them (wait for completion):
       Console.WriteLine("Waiting for completion...");
       Array.ForEach(threads, delegate(Thread t) { t.Join(); });
       Console.WriteLine("All threads complete");
    }
}

结果正如预期。请注意,当我们处理多个线程时,我们需要等待所有线程。我们可以通过保留所有线程的引用并对每个线程调用 Join 来等待线程完成。

Beginning thread execution...
Worker 0
Worker 1
Worker 2
Worker 3
Waiting for completion...
Worker 4
All threads complete

现在,在前面的示例中,我们使用了不带参数的 ThreadStart 委托。实际上,您需要将信息传递给各个线程。为此,您需要使用一个名为 ParamterizedThreadStart 的新委托。此委托指定一个具有单个 Object 类型参数且不返回任何内容的签名的方法。这是一个示例。请注意,我们通过使用此委托将数据传递给线程。

using System;
using System.Threading;
public static class Program {
    public static void Main() {
        ParameterizedThreadStart operation = 
              new ParameterizedThreadStart(WorkWithParameter);
        Thread theThread = new Thread(operation);
        theThread.Start("hello");
        // a second thread with (data) a different parameter
        Thread newThread = new Thread(operation);
        newThread.Start("goodbye");
    }
    private static void WorkWithParameter(object o)
    {
        string info = (string) o;
        for (int x = 0; x < 10; ++x)
        {
            Console.WriteLine("{0}: {1}", info, 
               Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(10);
        }
    }
}

输出

hello: 3
goodbye: 4
hello: 3
goodbye: 4
hello: 3
goodbye: 4
hello: 3
goodbye: 4
hello: 3
goodbye: 4
goodbye: 4
hello: 3
goodbye: 4
hello: 3
goodbye: 4
hello: 3
hello: 3
goodbye: 4
goodbye: 4
hello: 3

检查创建的方法 WorkWithParameter(object o)。这是一个接受单个 Object 参数的方法(因此可以是任何对象的引用)。要将其用作线程调用的起点,您可以创建一个 ParameterizedThreadStart 委托指向此新方法,并使用接受单个 object 参数的 Thread.Start 方法的重载。

去向何方

当初学者进入同步、并发、并行等主题时,线程主题有时会让他们感到困惑。让我们以 lock 语句为例。C# 中的 lock 语句实际上只是处理 System.Threading.Monitor 类类型的简写。因此,如果您要查看 lock() 实际解析的内容,您会发现如下代码:

using System;
using System.Threading;

public class WhatIsAThread
{
    private long refCount = 0;
    public void AddRef()
    {
        Interlocked.Increment(ref refCount);
    }
    
    public void Release()
    {
        if(Interlocked.Decrement(ref refCount) == 0)
        {
            GC.Collect();
        }
    }
}

internal class WorkerClass
{
    public void DoSomeWork()
    {
        
        lock(this)
        {
            
            for(int i = 0; i < 5; i++)
            {
                Console.WriteLine("Worker says: " + i + ", ");
            }
        }

        // The C# lock statmement is really...
        Monitor.Enter(this);
        try
        {
            // Do the work.
            For  (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Worker says: " + i + ", ");
            }
        }
        finally
        {
            Monitor.Exit(this);
        }
    }
}

public class MainClass
{

    public static int Main(string[] args)
    {
        // Make the worker object.
        WorkerClass w = new WorkerClass();
        Thread workerThreadA = new Thread(new ThreadStart(w.DoSomeWork));
        Thread workerThreadB = new Thread(new ThreadStart(w.DoSomeWork));
        Thread workerThreadC = new Thread(new ThreadStart(w.DoSomeWork));
        workerThreadA.Start();
        workerThreadB.Start();
        workerThreadC.Start();
        
        return 0;
    }
}

输出

Worker says: 0,
Worker says: 1,
Worker says: 2,
Worker says: 3,
Worker says: 4,
Worker says: 0,
Worker says: 1,
Worker says: 2,
Worker says: 3,
Worker says: 4,
Worker says: 0,
Worker says: 1,
Worker says: 2,
Worker says: 3,
Worker says: 4,

线程:深入了解

本文的这一部分将继续作为线程的参考,但也将包括线程可以执行的操作系统环境。在内核对象中,我们将介绍事件线程同步对象。检查环境可以帮助我们更好地理解如何有效地实现并发和多线程。线程创建的主题将在本文后面出现。我们还想知道为什么线程是有成本的:宽松地说,线程是很昂贵的。每个线程都提供:

  • 线程内核对象:操作系统为系统中创建的每个线程分配和初始化一个这样的数据结构。该数据结构包含一堆属性(稍后在本章讨论),这些属性描述了线程。此数据结构还包含称为线程上下文的内容。上下文是内存块,其中包含 CPU 寄存器的集合。当 Windows 在具有 x86 CPU 的机器上运行时,线程上下文会占用大约 700 字节的内存。对于 x64 和 IA64 CPU,上下文分别约为 1,240 和 2,500 字节的内存。
  • 线程环境块 (TEB):TEB 是在用户模式下分配和初始化的内存块(应用程序代码可以快速访问的地址空间)。TEB 占用 1 页内存(x86 和 x64 CPU 上为 4 KB,IA64 CPU 上为 8 KB)。TEB 包含线程异常处理链的头部。线程进入的每个 try 块都会在此链的头部插入一个节点;当线程退出 try 块时,会从链中移除该节点。此外,TEB 包含线程的线程局部存储数据以及图形设备接口 (GDI) 和 OpenGL 图形使用的一些数据结构。
  • 用户模式堆栈:用户模式堆栈用于局部变量和传递给方法的参数。它还包含指示线程在当前方法返回时应执行何处的地址。默认情况下,Windows 为每个线程的用户模式堆栈分配 1 MB 的内存。
  • 内核模式堆栈:当应用程序代码将参数传递给操作系统中的内核模式函数时,内核模式堆栈也用于此。出于安全原因,Windows 会将从用户模式代码传递到内核的任何参数从线程的用户模式堆栈复制到线程的内核模式堆栈。复制后,内核可以验证参数值,并且由于应用程序代码无法访问内核模式堆栈,因此在验证参数值并操作系统内核代码开始对其进行操作后,应用程序无法修改参数值。此外,内核调用其内部的方法,并使用内核模式堆栈来传递其自身的参数、存储函数的局部变量以及存储返回地址。在 32 位 Windows 系统上运行时,内核模式堆栈为 12 KB,在 64 位 Windows 系统上运行时为 24 KB。

为单个程序使用多个线程可以同时运行程序的完全独立的部分。这称为并发,并且经常在服务器端应用程序中使用。使用线程将一个大任务分解成多个可以并发执行的部分称为并行。从概念上讲,线程是执行单元——代表程序正在执行的工作的执行上下文。Windows 必须为每个线程分配一个内核对象以及一组数据结构。每个线程都由 Windows 线程调度器映射到处理器,从而能够实际执行正在进行的工作。每个线程都有一个指向当前执行指令的指令指针。“执行”包括处理器获取下一条指令、对其进行解码,然后逐条指令地从线程的代码中发出。在编译代码的执行过程中,程序数据会例行地在寄存器和连接的主内存之间移动。虽然这些寄存器物理上位于处理器上,但一些易失性状态也属于线程。如果线程必须暂停,此状态将被捕获并保存在内存中,以便以后恢复。这样做使得线程可以像从未中断过一样继续进行相同的 IP 获取、解码和发出过程。将此状态从硬件保存或恢复到硬件的过程称为上下文切换。

执行上下文

与 Windows 一样,.NET 中的每个线程都有与之相关的数据,并且这些数据通常会传播到新线程。这些数据包括安全信息(IPrinciple 和线程标识)、本地化字符串以及来自 System.Transaction 的事务信息。默认情况下,执行上下文会流向辅助线程,但这代价很高:上下文切换是一个重量级操作。要访问当前的执行上下文,ExecutionContext 类提供了静态方法来控制上下文信息的流。因此,在 System.Threading 命名空间中,有一个 ExecutionContext 类,允许您控制线程的执行上下文如何从一个线程流向另一个线程。此类如下所示:

public sealed class ExecutionContext : IDisposable, ISerializable {
    [SecurityCritical]public static AsyncFlowControl SuppressFlow();
    public static void RestoreFlow();
    public static Boolean IsFlowSuppressed();
    // Less commonly used methods are not shown
}

您可以使用此类来抑制执行上下文的流,从而提高应用程序的性能。对于服务器应用程序,性能提升可能相当可观。对于客户端应用程序,性能提升不大,并且 SuppressFlow 方法被标记为 [SecurityCritical] 属性,这使得在某些客户端应用程序(如 Silverlight)中无法调用它。当然,您应该仅在辅助线程不需要或访问上下文信息时才抑制执行上下文的流。如果发起线程的执行上下文没有流向辅助线程,则辅助线程将使用最后与之关联的任何执行上下文。因此,辅助线程实际上不应执行任何依赖于执行上下文状态(例如用户的 Windows 标识)的代码。接下来的示例是 MSDN 代码,在 .NET 4.0 上编译时会发出警告。它在 .NET 2.0 上正常编译和执行。

using System;
using System.Threading;
using System.Security;
using System.Collections;
using System.Security.Permissions;
using System.Runtime.Serialization;
using System.Runtime.Remoting.Messaging;

namespace Contoso
{
    class ExecutionContextSample
    {
        static void Main()
        {
            try
            {
                Console.WriteLine("Executing Main in the primary thread.");
                FileDialogPermission fdp = new FileDialogPermission(
                    FileDialogPermissionAccess.OpenSave);
                fdp.Deny();
                // Capture the execution context containing the Deny.
                ExecutionContext eC = ExecutionContext.Capture();

                // Suppress the flow of the execution context.
                AsyncFlowControl aFC = ExecutionContext.SuppressFlow();
                Thread t1 = new Thread(new ThreadStart(DemandPermission));
                t1.Start();
                t1.Join();
                Console.WriteLine("Is the flow suppressed? " +
                    ExecutionContext.IsFlowSuppressed());
                Console.WriteLine("Restore the flow.");
                aFC.Undo();
                Console.WriteLine("Is the flow suppressed? " +
                    ExecutionContext.IsFlowSuppressed());
                Thread t2 = new Thread(new ThreadStart(DemandPermission));
                t2.Start();
                t2.Join();
                // Remove the Deny.
                CodeAccessPermission.RevertDeny();
                // Capture the context that does not contain the Deny.
                ExecutionContext eC2 = ExecutionContext.Capture();
                // Show that the Deny is no longer present.
                Thread t3 = new Thread(new ThreadStart(DemandPermission));
                t3.Start();
                t3.Join();

                // Set the context that contains the Deny.
                // Show the deny is again active.
                Thread t4 = new Thread(new ThreadStart(DemandPermission));
                t4.Start();
                t4.Join();
                // Demonstrate the execution context methods.
                ExecutionContextMethods();
                Console.WriteLine("Demo is complete, press Enter to exit.");
                Console.Read();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
        // Execute the Demand.
        static void DemandPermission()
        {
            try
            {
                Console.WriteLine("In the thread executing a Demand for " +
                    "FileDialogPermission.");
                new FileDialogPermission(
                    FileDialogPermissionAccess.OpenSave).Demand();
                Console.WriteLine("Successfully demanded " +
                    "FileDialogPermission.");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        static void ExecutionContextMethods()
        {
            // Generate a call context for this thread.
            ContextBoundType cBT = new ContextBoundType();
            cBT.GetServerTime();
            ExecutionContext eC1 = ExecutionContext.Capture();
            ExecutionContext eC2 = eC1.CreateCopy();
            Console.WriteLine("The hash code for the first execution " +
                "context is: " + eC1.GetHashCode());

            // Create a SerializationInfo object to be used for getting the
            // object data.
            SerializationInfo sI = new SerializationInfo(
                typeof(ExecutionContext),
                new FormatterConverter());

            eC1.GetObjectData(
                sI,
                new StreamingContext(StreamingContextStates.All));

            LogicalCallContext lCC = (LogicalCallContext)sI.GetValue(
                "LogicalCallContext",
                typeof(LogicalCallContext));

            // The logical call context object should contain the previously
            // created call context.
            Console.WriteLine("Is the logical call context information " +
                "available? " + lCC.HasInfo);
        }
    }

    // One means of communicating between client and server is to use the
    // CallContext class. Calling CallContext effectivel puts the data in a thread
    // local store. This means that the information is available to that thread
    // or that logical thread (across application domains) only.
    [Serializable]
    public class CallContextString : ILogicalThreadAffinative
    {
        String _str = "";

        public CallContextString(String str)
        {
            _str = str;
            Console.WriteLine("A CallContextString has been created.");
        }

        public override String ToString()
        {
            return _str;
        }
    }

    public class ContextBoundType : ContextBoundObject
    {
        private DateTime starttime;

        public ContextBoundType()
        {
            Console.WriteLine("An instance of ContextBoundType has been " +
                "created.");
            starttime = DateTime.Now;
        }
        [SecurityPermissionAttribute(SecurityAction.Demand, 
            Flags = SecurityPermissionFlag.Infrastructure)]
        public DateTime GetServerTime()
        {
            Console.WriteLine("The time requested by a client.");
            // This call overwrites the client's
            // CallContextString.
            CallContext.SetData(
                "ServerThreadData",
                new CallContextString("This is the server side replacement " +
                "string."));
            return DateTime.Now;
        }
    }
}

输出

Executing Main in the primary thread.
In the thread executing a Demand for FileDialogPermission.
Successfully demanded FileDialogPermission.
Is the flow suppressed? True
Restore the flow.
Is the flow suppressed? False
In the thread executing a Demand for FileDialogPermission.
Request for the permission of type 'System.Security.Permissions.FileDialogPermis
sion, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e08
9' failed.
In the thread executing a Demand for FileDialogPermission.
Successfully demanded FileDialogPermission.
In the thread executing a Demand for FileDialogPermission.
Successfully demanded FileDialogPermission.
An instance of ContextBoundType has been created.
The time requested by a client.
A CallContextString has been created.
The hash code for the first execution context is: 54267293
Is the logical call context information available? True
Demo is complete, press Enter to exit.

线程在进程内运行,但进程不运行:线程运行。

.NET Framework 中的进程与 Windows 中的进程一一对应。进程的主要目的是管理每个程序的资源;这包括进程中所有线程共享的虚拟地址空间、句柄表、已加载 DLL 的共享列表(映射到相同的地址空间)以及存储在进程环境块 (PEB) 中的各种其他进程范围数据。由于这种隔离,一个进程中的问题通常不会影响另一个进程。然而,由于进程间通信和机器范围的共享资源——例如文件、内存映射 I/O 和命名内核对象——有时一个进程可能会干扰另一个进程。

Windows 提供了四个用于线程和进程同步的对象——互斥体、信号量、事件(所有这些都是内核对象)和一个临界区对象。事件是一个内核同步对象,用于向其他线程发出信号,表明某个事件(例如消息)已发生。事件提供的关键功能是,当单个事件被信号化时,多个线程可以同时从等待中释放。在 .NET 中,有一个 Event 类,其中事件(一种内核对象)可以是两种类型之一:自动重置和手动重置。当一个自动重置事件被信号化时,第一个等待该事件的对象会将其设置回非信号化状态。此行为类似于互斥体。相反,手动重置事件允许所有等待它的线程一直处于未阻塞状态,直到有东西手动将事件重置为非信号化状态。这些事件在 .NET 中表示为 AutoResetEvent 类和 ManualResetEvent 类。这两个类都继承自一个通用的 EventWaitHandle 类(它本身处理 WaitHandle 类)。假设我们想编写一些代码来调度线程池上的工作。

using System;
using System.Threading;

public sealed class Program {
    private static void MyThreadPoolWorker(object state)
    {
          ManualResetEvent mre = (ManualResetEvent)state;

          // Do some work; this executes on a thread from the thread-pool...
          Console.WriteLine("Work occurring on the thread-pool: {0}",
                Thread.CurrentThread.ManagedThreadId);

          // Now set the event to let our caller know we're done:
          mre.Set();
    }

    public static void Main()
    {
          using (ManualResetEvent mre = new ManualResetEvent(false))
          {
              ThreadPool.QueueUserWorkItem(new WaitCallback(MyThreadPoolWorker), mre);

              // Continue working while the thread-pool executes the work item:
              Console.WriteLine("Continuing work on the main thread: {0}",
                      Thread.CurrentThread.ManagedThreadId);

              // Lastly, wait for the thread-pool to finish:
              mre.WaitOne();
          }
    }
}

此示例显示注册等待回调以处理事件

using System;
using System.Threading;

public sealed class Program {
    public static void Main()
    {
        using (EventWaitHandle ewh = new ManualResetEvent(false))
        using (EventWaitHandle callbackDoneEvent = new ManualResetEvent(false))
        {
            // Register our callback to be fired when the event is set:
            ThreadPool.RegisterWaitForSingleObject(ewh,
                delegate {
                    Console.WriteLine("Callback fired: {0}", 
                         Thread.CurrentThread.ManagedThreadId);
                    callbackDoneEvent.Set();
                }, null, Timeout.Infinite, true);

            // Now set the event. Notice the callback fires
            // on a separate (thread-pool) thread.
            Console.WriteLine("Setting the event: {0}", 
                              Thread.CurrentThread.ManagedThreadId);
            ewh.Set();

            //  wait for the callback to complete
            callbackDoneEvent.WaitOne();
        }
    }
}

结果

Setting the event: 1
Callback fired: 4

Continuing work on the main thread: 1
Work occurring on the thread-pool: 3

线程创建

要在 .NET Framework 中创建新线程,必须首先通过 Threadd 的众多构造函数之一创建 Thread 对象。

public delegate void ThreadStart();
public delegate void ParameterizedThreadStart(object obj);
public class Thread
{
   public Thread(ThreadStart start);
   
   public Thread(ThreadStart start, int maxStackSize);
   
   public Thread(ParameterizedThreadStart start);
   
   public Thread(ParameterizedThreadStart start, int maxStackSize);
   
    . . . 
}

回想一下,使用基于 ParameterizedThreadStart 的构造函数创建的线程允许调用者将对象引用参数传递给 Start 方法(作为参数),然后可以在新线程的 Start 例程中作为 obj 访问。

//example using delegates

using System;
using System.Threading;

public static class Program
{
    public static void Main()
    {
        Thread newThread = new Thread(
            new ParameterizedThreadStart(MyThreadStart));

        Console.WriteLine("{0}: Created thread (ID {1})",
            Thread.CurrentThread.ManagedThreadId,
            newThread.ManagedThreadId);

        newThread.Start("Hello world"); // Begin execution.

        newThread.Join(); // Wait for the thread to finish.

        Console.WriteLine("{0}: Thread exited",
            Thread.CurrentThread.ManagedThreadId);
    }

    private static void MyThreadStart(object obj)
    {
        Console.WriteLine("{0}: Running: {1}",
            Thread.CurrentThread.ManagedThreadId, obj);
    }
}

结果

1: Created thread (ID 3)
3: Running: Hello world
1: Thread exited

这是一个使用匿名委托的线程创建示例

using System;
using System.Threading;
public static class Program
{
    public static void Main()
    {
        Thread newThread = new Thread(delegate(object obj)
        {
            Console.WriteLine("{0}: Running {1}",
                Thread.CurrentThread.ManagedThreadId, obj);
        });
        newThread.Start("Hello world (with anon delegates)");
        newThread.Join();
    }
}

输出

3: Running Hello world (with anon delegates)

最后,这是一个使用 lambda 表达式创建线程的示例

using System;
using System.Threading;

public static class Program
{
   public static void Main()
   {
      Thread newThread = new Thread(obj =>
            Console.WriteLine("{0}: Running {1}",
                Thread.CurrentThread.ManagedThreadId, obj)
        );
      newThread.Start("Hello world (with lambdas)");
      newThread.Join();
   }
}

输出

3: Running Hello world (with lambdas)

那么我们得到了什么?我们看到一个线程退出了。如果线程被突然终止,系统的状态是否相同?我们知道应用程序会经常阻塞执行,以便它可以执行 I/O;例如,读取磁盘上的扇区、与网络端点通信等。但是 UI 通过处理入队到每个 UI 线程的消息队列的消息来工作。几种阻塞类型使 UI 的消息循环能够运行。但其他则不能。这可能导致消息(例如 WM_CLOSEWM_PAINT 等)在 I/O 完成(即同步运行)之前滞留在队列中。对于长时间运行的操作,这可能导致 UI 变得无响应。以下是一个小型 Windows Forms 程序示例,演示了维护 UI 的最基本概念。

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
class Program : Form
{
   private System.Windows.Forms.ProgressBar _ProgressBar;

   [STAThread]
    static void Main()
    {
        Application.Run(new Program());
    }
    public Program()
    {
        InitializeComponent();
        ThreadStart threadStart = Increment;
        threadStart.BeginInvoke(null, null);
    }
    void UpdateProgressBar()
    {
        if (_ProgressBar.InvokeRequired)
        {
            MethodInvoker updateProgressBar = UpdateProgressBar;
            _ProgressBar.Invoke(updateProgressBar);
        }
        else
        {
            _ProgressBar.Increment(1);
        }    
    }
    private void Increment()
    {
        for (int i = 0; i < 100; i++)
        {
            UpdateProgressBar();
            Thread.Sleep(100);
        }
    }
    private void InitializeComponent()
    {
        _ProgressBar = new ProgressBar();
        SuspendLayout();
        _ProgressBar.Location = new Point(13, 17);
        _ProgressBar.Size = new Size(267, 19);
        ClientSize = new Size(292, 53);
        Controls.Add(this._ProgressBar);
        Text = "Multithreading in Windows Forms";
        ResumeLayout(false);
    }
}

1.JPG

CLR 线程池的基础知识涉及将一批工作排队以便由线程池运行,使用线程池在异步 I/O 完成时运行某些工作,使用计时器定期或按时执行工作,以及/或计划在内核对象被信号化时运行某些工作。MSDN 上有关于并发的文章,其中描述了管理线程池顺序或完全构建自定义线程池以优化执行上下文。

参考文献

  • Jeffrey Richter 著,《CLR via C#》,第 3 版
  • Joe Duffy 著,《Professional .NET Framework 2.0》
  • Microsoft .NET Framework 2.0 Application Development Foundation
© . All rights reserved.