C#学习笔记2:多线程

理解进程与线程

进程 (Process) 是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。

应用程序域 (AppDomain) 是一个程序运行的逻辑区域,它可以视为一个轻量级的进程,.NET的程序集正是在应用程序域中运行的,一个进程可以包含有多个应用程序域,一个应用程序域也可以包含多个程序集。在一个应用程序域中包含了一个或多个上下文context,使用上下文CLR就能够把某些特殊对象的状态放置在不同容器当中。

线程 (Thread) 是进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器 (Thread Local Storage,TLS) 组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。

一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。

线程的生命周期

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

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

  • 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。

  • 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。

  • 不可运行状态:下面的几种情况下线程是不可运行的:

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

线程的操作

System.Threading.Thread类

System.Threading.Thread 是用于控制线程的基础类,通过Thread可以控制当前应用程序域中线程的创建、挂起、停止、销毁。

属性

  • ManagedThreadId 是确认线程的唯一标识符,程序在大部分情况下都是通过 Thread.ManagedThreadId 来辨别线程的。而 Name 是一个可变值,在默认时候,Name 为一个空值 Null,开发人员可以通过程序设置线程的名称,但这只是一个辅助功能。
  • .NET 为线程设置了 Priority 属性来定义线程执行的优先级别,里面包含5个选项(Lowest, BelowNormal, Normal, AboveNormal, Highest),其中 Normal 是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别
  • 通过 ThreadState 可以检测线程是处于 Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。
  • 一个应用程序域中可能包括多个上下文,而通过 CurrentContext 可以获取线程当前的上下文。
  • CurrentThread 是最常用的一个属性,它是用于获取当前运行的线程。

方法

程序示例

通过Thread显示当前线程信息:

static void Main(string[] args)
    {
        Thread thread = Thread.CurrentThread;
        thread.Name = "Main Thread";
        string threadMessage = string.Format("Thread ID:{0}\n    Current AppDomainId:{1}\n    "+
            "Current ContextId:{2}\n    Thread Name:{3}\n    "+
            "Thread State:{4}\n    Thread Priority:{5}\n",
            thread.ManagedThreadId, Thread.GetDomainID(), Thread.CurrentContext.ContextID,
            thread.Name, thread.ThreadState, thread.Priority);
        Console.WriteLine(threadMessage);
        Console.ReadKey();
    }

结果如下:

实现多线程

无参数传入:使用ThreadStart委托

示例如下,首先在 Message 类中建立一个方法 ShowMessage(),里面显示了当前运行线程的 Id,并使用 Thread.Sleep(int) 方法模拟部分工作。在 main() 中通过 ThreadStart委托 绑定Message对象的 ShowMessage()方法,然后通过 Thread.Start() 执行异步方法。

public class Message
  {
      public void ShowMessage()
      {
          string message = string.Format("Async threadId is :{0}",
                                          Thread.CurrentThread.ManagedThreadId);
          Console.WriteLine(message);

          for (int n = 0; n < 10; n++)
          {
              Thread.Sleep(300);   
              Console.WriteLine("The number is:" + n.ToString()); 
          }
      }
  }

  class Program
  {
      static void Main(string[] args)
      {
          Console.WriteLine("Main threadId is:"+
                            Thread.CurrentThread.ManagedThreadId);
          Message message=new Message();
          Thread thread = new Thread(new ThreadStart(message.ShowMessage));
          thread.Start();
          Console.WriteLine("Do something ..........!");
          Console.WriteLine("Main thread working is complete!");

      }
  }

在调用 Thread.Start()方法 后,系统以异步方式运行 Message.ShowMessage(),而主线程的操作是继续执行的,在 Message.ShowMessage() 完成前,主线程已完成所有的操作。结果如下:

参数传入:三种方式

使用 ThreadStart委托 的缺点在于无法给线程传递参数,下面介绍三种方式以实现给线程传递参数:

  • 使用ParameterizedThreadStart委托

如果使用了ParameterizedThreadStart委托,线程的入口必须有一个object类型的参数,且返回类型为void。如下面的例子:

class Program
{
    static void Main(string[] args)
    {
        string hello = "hello world";

        //这里也可简写成Thread thread = new Thread(ThreadMainWithParameters);
        //但是为了让大家知道这里用的是ParameterizedThreadStart委托,就没有简写了
        Thread thread = new Thread(new ParameterizedThreadStart(ThreadMainWithParameters));
        thread.Start(hello);

        Console.Read();
    }

    static void ThreadMainWithParameters(object obj)
    {
        string str = obj as string;
        if(!string.IsNullOrEmpty(str))
            Console.WriteLine("Running in a thread,received: {0}", str);
    }
}

这里稍微有点麻烦的就是 ThreadMainWithParameters方法 里的参数必须是 object类型 的,我们需要进行类型转换。为什么参数必须是object类型呢?看看ParameterizedThreadStart委托的声明就知道了。

public delegate void ParameterizedThreadStart(object obj);   //ParameterizedThreadStart委托的声明
  • 创建自定义类

定义一个类,在其中定义需要的字段,将线程的主方法定义为类的一个实例方法。如下面的例子:

public class MyThread
{
    private string data;

    public MyThread(string data)
    {
        this.data = data;
    }

    public void ThreadMain()
    {
        Console.WriteLine("Running in a thread,data: {0}", data);
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyThread myThread = new MyThread("hello world");

        Thread thread = new Thread(myThread.ThreadMain);
        thread.Start();

        Console.Read();
    }
}    
  • 使用匿名方法

示例如下:

class Program
{
    static void Main(string[] args)
    {
        string hello = "hello world";

        //如果写成Thread thread = new Thread(ThreadMainWithParameters(hello));这种形式,编译时就会报错
        Thread thread = new Thread(() => ThreadMainWithParameters(hello));
        thread.Start();

        Console.Read();
    }

    static void ThreadMainWithParameters(string str)
    {
         Console.WriteLine("Running in a thread,received: {0}", str);
    }

通过反编译体现了第三种方法其实和第二种方法其实是一样的。

关于async action的问题

特别需要注意 new Task(async () => await FuncAsync()) 这种类似的写法,这种写法后面是不能await到函数执行结束的结果的。

见下面的例子:

class TestTask
{
    private async static Task TestAsync(int opt)
    {
        await Task.Delay(1000);
        Console.WriteLine("opt {0}: finish async task.", opt);
    }

    private static void TestSync(int opt)
    {
        Thread.Sleep(1000);
        Console.WriteLine("opt {0}: finish sync.", opt);
    }

    private async static Task StartAsync()
    {
        List<Task> tasks = new List<Task>();
        tasks.Add(new Task(() => TestSync(1)));
        tasks.Add(new Task(async () => await TestAsync(2))); // 这条语句存在问题,不能await到函数执行的结果

        foreach (var task in tasks)
        {
            task.Start();
        }

        // 以下t1, t2, t3均不需要task.Start()方法

        //var t1 = new Task<Task>(async () => await TestAsync(2));
        //t1.Start();
        //tasks.Add(t1.Unwrap());

        //var t2 = Task.Factory.StartNew(async()=>await TestAsync(2));
        //tasks.Add(t2.Unwrap());

        //var t3 = Task.Run(async () => await TestAsync(2));
        //tasks.Add(t3); 

        Task.WaitAll(tasks.ToArray());

        Console.WriteLine("finish start.");
    }

    static void Main()
    {
        StartAsync().GetAwaiter().GetResult();
    }
}

执行结果为如下:

可以看出没有等到 TestAsync(2) 的返回结果。

应该将上述存在问题的语句改为如下中的任意一种即可:

// 以下t1, t2, t3均不需要task.Start()方法

var t1 = new Task<Task>(async () => await TestAsync(2));
t1.Start();
tasks.Add(t1.Unwrap());

var t2 = Task.Factory.StartNew(async () => await TestAsync(2));
tasks.Add(t2.Unwrap());

var t3 = Task.Run(async () => await TestAsync(2));
tasks.Add(t3); 

此时执行结果为:


本文结束,感谢欣赏。

欢迎转载,请注明本文的链接地址:

http://www.jeyzhang.com/csharp-learning-notes-2-multithreading.html

参考资料

C#综合揭秘——细说多线程

C#多线程 | 菜鸟教程

C# 给多线程传参的三种方式