菜鸟教程 -- 学的不仅是技术,更是梦想!

C# 教程
(追記) (追記ここまで)

C# 多线程

线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果您的应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。

线程是轻量级进程。一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。

到目前为止我们编写的程序是一个单线程作为应用程序的运行实例的单一的过程运行的。但是,这样子应用程序同时只能执行一个任务。为了同时执行多个任务,它可以被划分为更小的线程。

线程生命周期

线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。

下面列出了线程生命周期中的各种状态:

  • 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。
  • 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
  • 不可运行状态:下面的几种情况下线程是不可运行的:

    • 已经调用 Sleep 方法
    • 已经调用 Wait 方法
    • 通过 I/O 操作阻塞
  • 死亡状态:当线程已完成执行或已中止时的状况。

主线程

在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程

当 C# 程序开始执行时,主线程自动创建。使用 Thread 类创建的线程被主线程的子线程调用。您可以使用 Thread 类的 CurrentThread 属性访问线程。

下面的程序演示了主线程的执行:

实例

using System;
using System.Threading;

namespace MultithreadingApplication
{
class MainThreadProgram
{
static void Main(string[] args)
{
Thread th = Thread.CurrentThread;
th.Name = "MainThread";
Console.WriteLine("This is {0}", th.Name);
Console.ReadKey();
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

This is MainThread

Thread 类常用的属性和方法

下表列出了 Thread 类的一些常用的 属性:

属性描述
CurrentContext获取线程正在其中执行的当前上下文。
CurrentCulture获取或设置当前线程的区域性。
CurrentPrincipal获取或设置线程的当前负责人(对基于角色的安全性而言)。
CurrentThread获取当前正在运行的线程。
CurrentUICulture获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源。
ExecutionContext获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive获取一个值,该值指示当前线程的执行状态。
IsBackground获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread获取一个值,该值指示线程是否属于托管线程池。
ManagedThreadId获取当前托管线程的唯一标识符。
Name获取或设置线程的名称。
Priority获取或设置一个值,该值指示线程的调度优先级。
ThreadState获取一个值,该值包含当前线程的状态。

下表列出了 Thread 类的一些常用的 方法:

序号方法名 & 描述
1public void Abort()
在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程。
2public static LocalDataStoreSlot AllocateDataSlot()
在所有的线程上分配未命名的数据槽。为了获得更好的性能,请改用以 ThreadStaticAttribute 属性标记的字段。
3public static LocalDataStoreSlot AllocateNamedDataSlot( string name)
在所有线程上分配已命名的数据槽。为了获得更好的性能,请改用以 ThreadStaticAttribute 属性标记的字段。
4public static void BeginCriticalRegion()
通知主机执行将要进入一个代码区域,在该代码区域内线程中止或未经处理的异常的影响可能会危害应用程序域中的其他任务。
5public static void BeginThreadAffinity()
通知主机托管代码将要执行依赖于当前物理操作系统线程的标识的指令。
6public static void EndCriticalRegion()
通知主机执行将要进入一个代码区域,在该代码区域内线程中止或未经处理的异常仅影响当前任务。
7public static void EndThreadAffinity()
通知主机托管代码已执行完依赖于当前物理操作系统线程的标识的指令。
8public static void FreeNamedDataSlot(string name)
为进程中的所有线程消除名称与槽之间的关联。为了获得更好的性能,请改用以 ThreadStaticAttribute 属性标记的字段。
9public static Object GetData( LocalDataStoreSlot slot )
在当前线程的当前域中从当前线程上指定的槽中检索值。为了获得更好的性能,请改用以 ThreadStaticAttribute 属性标记的字段。
10public static AppDomain GetDomain()
返回当前线程正在其中运行的当前域。
11public static AppDomain GetDomainID()
返回唯一的应用程序域标识符。
12public static LocalDataStoreSlot GetNamedDataSlot( string name )
查找已命名的数据槽。为了获得更好的性能,请改用以 ThreadStaticAttribute 属性标记的字段。
13public void Interrupt()
中断处于 WaitSleepJoin 线程状态的线程。
14public void Join()
在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。此方法有不同的重载形式。
15public static void MemoryBarrier()
按如下方式同步内存存取:执行当前线程的处理器在对指令重新排序时,不能采用先执行 MemoryBarrier 调用之后的内存存取,再执行 MemoryBarrier 调用之前的内存存取的方式。
16public static void ResetAbort()
取消为当前线程请求的 Abort。
17public static void SetData( LocalDataStoreSlot slot, Object data )
在当前正在运行的线程上为此线程的当前域在指定槽中设置数据。为了获得更好的性能,请改用以 ThreadStaticAttribute 属性标记的字段。
18public void Start()
开始一个线程。
19public static void Sleep( int millisecondsTimeout )
让线程暂停一段时间。
20public static void SpinWait( int iterations )
导致线程等待由 iterations 参数定义的时间量。
21public static byte VolatileRead( ref byte address )
public static double VolatileRead( ref double address )
public static int VolatileRead( ref int address )
public static Object VolatileRead( ref Object address )

读取字段值。无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值。此方法有不同的重载形式。这里只给出了一些形式。
22public static void VolatileWrite( ref byte address, byte value )
public static void VolatileWrite( ref double address, double value )
public static void VolatileWrite( ref int address, int value )
public static void VolatileWrite( ref Object address, Object value )

立即向字段写入一个值,以使该值对计算机中的所有处理器都可见。此方法有不同的重载形式。这里只给出了一些形式。
23public static bool Yield()
导致调用线程执行准备好在当前处理器上运行的另一个线程。由操作系统选择要执行的线程。

创建线程

线程是通过扩展 Thread 类创建的。扩展的 Thread 类调用 Start() 方法来开始子线程的执行。

下面的程序演示了这个概念:

实例

using System;
using System.Threading;

namespace MultithreadingApplication
{
class ThreadCreationProgram
{
public static void CallToChildThread()
{
Console.WriteLine("Child thread starts");
}

static void Main(string[] args)
{
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
Console.ReadKey();
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

In Main: Creating the Child thread
Child thread starts

管理线程

Thread 类提供了各种管理线程的方法。

下面的实例演示了 sleep() 方法的使用,用于在一个特定的时间暂停线程。

实例

using System;
using System.Threading;

namespace MultithreadingApplication
{
class ThreadCreationProgram
{
public static void CallToChildThread()
{
Console.WriteLine("Child thread starts");
// 线程暂停 5000 毫秒
int sleepfor = 5000;
Console.WriteLine("Child Thread Paused for {0} seconds",
sleepfor / 1000);
Thread.Sleep(sleepfor);
Console.WriteLine("Child thread resumes");
}

static void Main(string[] args)
{
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
Console.ReadKey();
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
Child thread resumes

销毁线程

Abort() 方法用于销毁线程。

通过抛出 threadabortexception 在运行时中止线程。这个异常不能被捕获,如果有 finally 块,控制会被送至 finally 块。

下面的程序说明了这点:

实例

using System;
using System.Threading;

namespace MultithreadingApplication
{
class ThreadCreationProgram
{
public static void CallToChildThread()
{
try
{

Console.WriteLine("Child thread starts");
// 计数到 10
for (int counter = 0; counter <= 10; counter++)
{
Thread.Sleep(500);
Console.WriteLine(counter);
}
Console.WriteLine("Child Thread Completed");

}
catch (ThreadAbortException e)
{
Console.WriteLine("Thread Abort Exception");
}
finally
{
Console.WriteLine("Couldn't catch the Thread Exception");
}

}

static void Main(string[] args)
{
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
// 停止主线程一段时间
Thread.Sleep(2000);
// 现在中止子线程
Console.WriteLine("In Main: Aborting the Child thread");
childThread.Abort();
Console.ReadKey();
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception 
AI 思考中...

8 篇笔记 写笔记

  1. #0
    84

    线程函数通过委托传递,可以不带参数,也可以带参数(只能有一个参数),可以用一个类或结构体封装参数:

    using System;
    using System.Threading;
    namespace Test
    {
     class Program
     {
     static void Main(string[] args)
     {
     Thread t1 = new Thread(new ThreadStart(TestMethod));
     Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
     t1.IsBackground = true;
     t2.IsBackground = true;
     t1.Start();
     t2.Start("hello");
     Console.ReadKey();
     }
     public static void TestMethod()
     {
     Console.WriteLine("不带参数的线程函数");
     }
     public static void TestMethod(object data)
     {
     string datastr = data as string;
     Console.WriteLine("带参数的线程函数,参数为:{0}", datastr);
     }
     }
    }
    
    9年前 (2017年03月10日)
  2. #0

    线程的销毁

    (1)线程自动销毁

    Thread a=new Thread(Method) th.start();
    

    线程方法Method执行完结,线程a也自动终止.

    (2)如果是无限循环需要手动销毁

    窗体应用程序的线程间通信

    .net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。

    线程函数通过委托传递,是一种安全的线程间通信的方式。也是委托的功能之一。

    但C#也可以关闭线程安全保护,自由的调用其他线程生成的控件。只要加上

    Control.CheckForIllegalCrossThreadCalls = false;

    但不推荐使用。自己练习时可以写着玩一下。

    9年前 (2017年04月21日)
  3. #0

    ddwzdd

    ddw***@gmail.com

    53

    本篇文章的第二段代码创建线程中,在2.0以后可以直接执行子线程,这样一来程序可以省略Main函数中的第一行代码。运行结果一样,似乎程序会更简单易懂点。如下:

    using System.Threading.Tasks;
    using System.Threading;
    namespace ConsoleApp
    {
     class Program
     {
     public static void CallToChildThread()
     {
     Console.WriteLine("Child thread starts");
     }
     static void Main(string[] args)
     {
     //ThreadStart childref = new ThreadStart(CallToChildThread);
     Console.WriteLine("In Main: Creating the Child thread");
     Thread childThread = new Thread(CallToChildThread);
     childThread.Start();
     Console.ReadKey();
     }
     }
    }

    ddwzdd

    ddw***@gmail.com

    9年前 (2017年09月30日)
  4. #0

    WayneGuo

    guo***[email protected]

    51

    C# 在 4.0 以后一共有3种创建线程的方式:

    • 1.Thread 自己创建的独立的线程, 优先级高,需要使用者自己管理。
    • 2.ThreadPool 有 .Net 自己管理, 只需要把需要处理的方法写好, 然后交个.Net Framework, 后续只要方法执行完毕, 则自动退出。
    • 3.Task 4.0 以后新增的线程操作方式, 类似 ThreadPool, 但效率测试比ThreadPool略高, Task对多核的支持更为明显,所以在多核的处理器中, Task的优势更为明显。
    class Program
    { 
     static void Main(string[] args)
     { //独立创建线程
     Thread t = new Thread(ThreadProcess);
     t.Start(new object());
     
     //线程池
     ThreadPool.QueueUserWorkItem(ThreadProcess, new object());
     //Task方式创建线程
     System.Threading.Tasks.Task.Factory.StartNew(ThreadProcess, new object());
     //需要手动终止,当然现在终止可能线程还未运行完成,
     t.Abort();
     }
     private static void ThreadProcess(object tag)
     {
     int i = 100;
     while (i > 0)
     {
     Console.WriteLine(string.Format("i:{0} ", i));
     Thread.Sleep(10);
     i--;
     }
     }
    }

    WayneGuo

    guo***[email protected]

    9年前 (2018年01月17日)
  5. #0

    樱花树

    100***[email protected]

    39

    笔记三的补充:

    Thread childThread = new Thread( new ThreadStart(CallToChildThread));
    Thread childThread = new Thread(CallToChildThread);

    这两种写法的效果是一样的。都是创建一个线程。后者只是 C# 的语法,编译时编译器会自动转换成第一种的形式。ThreadStart 是线程的入口,可以理解为一个函数指针,指向线程将要运行的函数。

    樱花树

    100***[email protected]

    8年前 (2018年06月12日)
  6. #0

    wangtao26-30飞日本

    368***[email protected]

    19

    线程函数通过委托传递,可以不带参数,也可以带参数(只能有一个参数),可以用一个类或结构体封装参数:

    using System;
    using System.Threading;
    namespace Test
    {
     class Program
     {
     static void Main(string[] args)
     {
     Thread t1 = new Thread(new ThreadStart(TestMethod));
     Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
     t1.IsBackground = true;
     t2.IsBackground = true;
     t1.Start();
     t2.Start("hello");
     Console.ReadKey();
     }
     public static void TestMethod()
     {
     Console.WriteLine("不带参数的线程函数");
     }
     public static void TestMethod(object data)
     {
     string datastr = data as string;
     Console.WriteLine("带参数的线程函数,参数为:{0}", datastr);
     }
     }
    }

    wangtao26-30飞日本

    368***[email protected]

    7年前 (2019年10月09日)
  7. #0

    kalimuci

    kal***[email protected]

    38

    @笔记三@笔记五的补充:

    Thread childThread = new Thread( new ThreadStart(CallToChildThread));
    Thread childThread = new Thread(CallToChildThread);

    经测试上面两种写法是有一点区别的,如果 CallToChildThread 方法没有重载,可以直接用下面的简化写法,如果有重载就需要先用 new ThreadStart 创建线程入口,不然编辑器会报"方法或属性调用不明确"错误。

    kalimuci

    kal***[email protected]

    6年前 (2020年12月30日)
  8. #0

    戈壁老王

    225***[email protected]

    参考地址

    31

    笔记三五七的补充

    一般情况下可以通用,一般形况下函数重载也不会报错,因为重载后会自己寻找类型匹配的函数(ThreadStart这个类参数是个委托,返回void,同时该委托没有参数)要是没有这个类型的,就会报错:"CallToChildThread"没有与委托"ThreadStart"匹配的重载。

    笔记六怎么就可以有参数呢?

    他创建的有参数的那个线程函数,不是一个 ThreadStart 类型的委托,而是 ParameterizedThreadStart 类型的。

    戈壁老王

    225***[email protected]

    参考地址

    5年前 (2021年07月30日)

点我分享笔记

  • 昵称 (必填)
  • 邮箱 (必填)
  • 引用地址

AltStyle によって変換されたページ (->オリジナル) /