C# 多线程

一、线程之间共享变量:

    1. 同对象中的变量(线程内部方法)
class ThreadTest {
    bool done;
    static void Main() {
        ThreadTest tt = new ThreadTest(); // Create a common instance
        new Thread(tt.Go).Start();
        tt.Go();
    }
    // Note that Go is now an instance method
    void Go() {
        if (!done) {
            done = true;
            Console.WriteLine("Done");
        }
    }
}
    1. 静态变量
class ThreadTest {
    static bool done; // Static fields are shared between all threads
    static void Main() {
        new Thread(Go).Start();
        Go();
    }
    static void Go() {
        if (!done) {
            done = true;
            Console.WriteLine("Done");
        }
    }
}

二、Join & Sleep

  1. Join 等待该线程执行完成之后。
  2. Sleep 暂停当前线程一段时间,Sleep(0) & Yield 同效,将当前进程置为等待状态,让出 CPU 给其他线程。

三、传递参数

  1. 匿名函数
    Thread t = new Thread ( () => Print ("Hello from t!") );
    
    new Thread (() => {...}).Start();
    
    new Thread (delegate(){...}).Start();
  2. Start 函数
    Thread t = new Thread (Print).Start ("Hello from t!");
    
    static void Print (object messageObj) //函数入参只能为 object 类型
  3. 注意参数的变化
    for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start();
    
    Output: 0223557799

四、线程命名

  1. 命名当前线程
    Thread.CurrentThread.Name = "main";
  2. 命名其他线程
    Thread worker = new Thread (Go);
    worker.Name = "worker";

五、前台线程和后台线程 (Foreground Threads & Background Threads)

  1. 前台线程可以阻止程序退出。除非所有前台线程都结束,否则 CLR不会关闭程序。(自己创建的线程默认前台)
  2. 后台线程有时候也叫 Daemon Thread 。他被 CLR 认为是不重要的执行路径,可以在任何时候舍弃。因此当所有的前台线程结束,即使还有后台线程在执行, CLR 也会关闭程序。(线程池中的线程默认后台)

线程的前后台与线程的优先级无关。

某些时候需要等待后台线程执行完成之后退出程序,如释放资源,可如下方式实现:

  1. 如果线程是自己创建的,可以使用 Join 函数
  2. 如果线程是线程池中的,可以使用 Wait 事件

六、线程优先级

线程的优先级如下:
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest } 

提升线程的优先级需要仔细认真的考虑,因为可能造成资源的死锁。

线程的优先级是在进程的优先级之下的,如果A进程中的A1线程想与B进程中B1线程争夺资源,首先应提高A进程的优先级:

using (Process p = Process.GetCurrentProcess())
p.PriorityClass = ProcessPriorityClass.High; 

ProcessPriorityClass.High为进程的最高等级,如果你的进程设置为 High,并且进入了无限循环,操作系统将会被锁定。由于这个原因,High 一般用于实时系统 Realtime,改进程在进入时间轮片之后,将不会出让 CPU 给任何进程。

七、异常处理

异常处理try/catch/finally需要写在函数内部,否则会导致主程序的崩溃,程序直接退出。

八、线程池

通过Tread创建线程需要消耗时间来组织资源创建线程,并且会消耗内存。使用线程池的线程不会有这些问题。进入线程池方式:

  1. Task Parallel Library(TPL .NET4.0+)
  2. 调用 ThreadPool.QueueUserWorkItem
  3. 异步委托(Asynchronous delegates)
  4. 通过BackgroundWorker

下列间接使用线程池构建:

  1. WCF、Remoting、ASP.NET、ASMX Web Service 服务
  2. System.Timers.Timer & System.Threading.Timer
  3. 框架方法,如WebClient、大部分的BeginXXX等异步方法
  4. PLINQ

使用线程池需注意:

  1. 线程池中的线程不可命名,调试困难
  2. 线程池中的线程均为后台线程
  3. 在应用程序生命周期的早期,阻塞线程可能会导致额外的延迟。

查询当前线程是否在线程池中:Thread.CurrentThread.IsThreadPoolThread.

九、使用线程池

  1. Task Parallel Library(TPL .NET4.0+)
    Task.Factory.StartNew (Go, arg1, arg2…);

    Go的参数可以不为object类型

    接收返回参数Task<TResult>:

    Task<string> task = Task.Factory.StartNew<string>(() => DownloadString ("http://www.linqpad.net"));
    
    string result = task.Result; //use the result, block the main thread until the task finished 

    当使用返回参数时,相当于在调用参数之前调用了 task.Wait();

  2. 调用 ThreadPool.QueueUserWorkItem

    当使用.NET为4.0之前版本时,我们只能使用ThreadPool.QueueUserWorkItem和Asynchronous delegates两种方式。

  3. 异步委托(Asynchronous delegates)

    与ThreadPool.QueueUserWorkItem区别是:Asynchronous delegates可以接收返回结果,并且可以在主线程中统一处理异常。

    1. 实例化委托
    2. 调用委托的BeginInvoke,使用IAsyncResult接收返回值
    3. 需要使用返回结果时,调用委托的EndInvoke,并且传递已保存的IAsyncResult对象。

    EndInvoke做的三件事:

    1. 等待异步委托执行完成。
    2. 接收返回结果(包括ref & out 参数)
    3. 抛出异常到主线程。

    拆分实现:

    method在BeginInvoke时传递,通过IAsyncResult.AsyncState传递。其中IAsyncResult.AsyncState为object类型,可以传递任何变量。

原文地址:https://www.cnblogs.com/zhuhc/p/4329122.html