理解进程与线程
进程 (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
参考资料