启动程序时,系统会在内存中创建一个新的进程。
进程是构成运行程序的资源的集合,这些资源包括 虚地址空间,文件句柄和许多程序运行所需的东西
在进程的内部,系统创建一个称为 线程 的内核对象,它表示真正执行的程序。一旦进程建立,系统会在Main方法的第一行语句中开始线程的执行。
一个进程可能有多个线程,它们将共享进程的资源,系统处理器执行所规划的单位是 线程。
1 async/await
特性
异步方法在处理完成之前就返回到调用方法。C#的 async/await
特性可以创建并使用异步方法,该特性有三个部分组成:
- 调用方法 :该方法调用异步方法,然后再异步方法执行其任务是继续执行
- 异步方法:该方法异步执行其工作,然后立即返回到调用方法
await
表达式:用于异步方法内部,指明需要异步执行的任务。一个异步方法可以含有多个await
表达式(至少一个)
1.1 异步方法
异步方法在完成其工作之前立即返回调用方法,然后再调用方法继续执行的时候完成其工作。
在语法上,异步方法具有如下特点:
- 方法头中包含
async
方法修饰符,而且必须在返回类型之前,async
关键字只是一个上下文关键字,也就是说它作为方法修饰符(或Lambda表达式修饰符/匿名方法修饰符)之外,还可以作为标识符 - 包含一个或多个
await
表达式,表示可以异步完成的任务 - 必须具备以下三种返回类型:
Task<T>
:如果调用方法要从调用中获取一个T
类型的值,异步方法的返回类型就必须是Task<T>
,调用方法可以读取Task
的Result
属性来获取这个T
类型Task
:如果调用方法不需要从异步方法中返回某个值,但需要检查异步方法的状态,那么异步方法可以返回一个Task
类型的对象void
:如果调用方法仅仅想执行异步方法,而不需与它做任何进一步的交互
- 异步方法的参数可以为任意类型任意数量,但不能为
out
或ref
参数 - 按照于欸的那个,异步方法的名称应该以
Async
为后缀 - 除了方法以外,Lambda表达式和匿名方法也可以作为异步对象
1.1.1 异步方法的控制流
异步方法的结构包含三个不同的区域:
- 第一个
await
表达式之前的部分 await
表达式:表示将被异步执行的任务- 后续部分:在
await
表达式之后出现的其余代码,包括执行环境,如所在线程小小,目前作用域内的变量值,以及当await
表达式完成后要重新执行所需的其他消息
由此可看出一个异步方法的控制流:
- 当达到
await
表达式时,异步方法将控制返回调用方法,如果方法的返回类型为Task
或Task<T>
类型,将创建一个Task
对象,表示需异步完成的任务和后续,然后把该Task
返回到调用方法。 - 当
await
表达式完成时,执行后续部分(后续部分本身可能含有其他await
表达式) - 当后续部分遇到
return
语句或到达方法末尾时,将:
- 如果方法返回类型为
void
,控制流将退出 - 如果方法返回类型为
Task
,后续部分设置Task
的属性并退出 - 如果返回类型为
Task<T>
,后续部分还将设置Task
对象的Result
属性
- 如果方法返回类型为
1.2 await
表达式
await
表达式制定了一个异步执行的任务,由一个 await
关键字和一个空闲对象(称为任务) 组成,这个任务通常为一个 Task
类型的对象。
await task
一个空闲对象即是一个 awaitable
类型的实例,awaitable
类型是指包含 GetAwaiter
方法的类型,该方法没有参数,返回一个称为 awaiter
类型的对象。
awaiter
类型包含以下成员:
bool IsCompleted {get;}
:是否完成void OCompleted(Action)
:完成后回调函数void GetResult();
T GetResult();
:T
为任意类型
而
Task
类,它就是awaitable
类型,
1.2.1 Task.Run()
尽管BCL中存在很多返回 Task<T>
类型对象的方法,但有时仍可能需要编写自己的方法,作为 await
表达式的任务,这时可以使用 Task.Run
方法来创建一个 Task
,关于 Task.Run
,有一点很重要那就是:它是在不同的线程上运行你的方法。
Task.Run
以 Func<TReturn>
委托为参数,Func<TReturn>
是一个预定义的委托,它不包含任何参数,返回值的类型为 TReturn
,因此要将你的方法传递给 Task.Run
方法,需要给予该方法创建一个委托。而 Task.Run
共有 8个重载,现列出最可能用到的四个委托类型的签名:
- 委托类型
Action
,签名void Action()
,含义:不需参数且无返回值的方法 - 委托类型
Func<TResult>
,签名TResult Func()
,含义:不需参数,返回TResult
类型对象的方法 - 委托类型
Func<Task>
,签名Task Func()
,含义:不需参数,返回Task
对象的方法 - 委托类型
Func<Task<TResult>>
,签名Task<TResult> Func()
,含义:不需参数,返回Task<T>
类型对象的方法
例如:
static class MyClass
{
public static async Task DoWorkAsync()
{
await Task.Run(() => Console.WriteLine(5.ToString())); // Action
Console.WriteLine((await Task.Run(() => 6)).ToString()); // TResult Func()
await Task.Run(() => Task.Run(() => Console.WriteLine(7.ToString()))); // Task Func()
int value = await Task.Run(() => Task.Run(() => 8)); // TResult Func()
Console.WriteLine(value.ToString());
}
class Program
{
static void Main()
{
Task t = MyClass.DoWorkAsync();
t.Wait();
Console.WriteLine("Press Enter key to exit");
Console.Read();
}
}
}
以上所有的委托都是无参数需求,如果添加的方法需要参数,则需要创建使用Lambda函数了,形如:
await Task.Run(() => GetSum(5, 6));
1.3 取消异步操作
可以在自定义的异步方法中添加一些特性,使得异步操作可以取消,其中位于 System.Threading.Tasks
命名空间中的两个类: CancellationToken
和 CancellationTokenSource
就是为此设计。
1.3.1 CancellationTokenSource
CancellationTokenSource
对象可以使用 Token
属性创建可分配到不同任务的 CancellationToken
对象,任何持有 CancellationTokenSource
的对象都可以调用其 Cancel
方法,这将会使得 CancellationToken
的 IsCancellationRequested
属性设置为 true
1.3.2 CancellationToken
CancellationToken
对象包含一个任务是否应被取消的信息,拥有 CancellationToken
对象的任务需要定期检查 CancellationToken
对象的 IsCancellationRequested
属性,如果属性值为 true
,则任务需停止其操作并返回
例子:
class Program
{
static void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
MyClass mc = new MyClass();
Task t = mc.RunAsync(token);
Thread.Sleep(3000);
cts.Cancel(); // 取消操作
t.Wait();
Console.WriteLine("Was Concelled:{0}", token.IsCancellationRequested);
}
class MyClass
{
// 引入 CancellationToken 实例作为参数
public async Task RunAsync(CancellationToken ct)
{
// 必须要先检查 IsCancellationRequested 的值
if (ct.IsCancellationRequested)
Return;
await Task.Run(() => CycleMethod(ct), ct);
}
void CycleMethod(CancellationToken ct)
{
Console.WriteLine("Starting CycleMethod");
const int max = 5;
for (int i=0; i<max; i++)
{
// 监控 IsCancellationRequested 的值
if (ct.IsCancellationRequested)
return;
Thread.Sleep(1000);
Console.WriteLine("{0} of {1} iterations completed", i+1, max);
}
}
}
}
1.4 调用方法中等待任务
调用方法可以调用任意多个异步方法并接受它们返回的 Task
对象,在某个节点上可能需要等待某个特殊的 Task
对象完成,然后在继续。为此,Task
类 提供实例方法 Wait
,可以在 Task
对象上调用这个方法来等待任务结束。
wait
方法用于单一 Task
对象,也可以使用 WaitAll
或 WaitAny
来等待一组 Task
对象
1.5 异步方法中异步地等待任务
如同调用方法,异步方法关于等待任务也有类似的 Task.WhenAll(taskObject)
和 Task.WhenAny(taskObject)
来实现(程序本来就是默认 Task.WhenAll
,即后续部分会等待 await
表达式执行完之后执行)。
1.6 Task.Delay
方法
Task.Delay
方法创建一个 Task
对象,该对象将暂停其在线程中的处理,并在一定时间之后完成,与 Thread.Sleep
阻塞线程不同的是,Task.Delay
不会阻塞线程,线程可以继续处理其他工作。
eg:
await Task.Delay(1000);
1.7 Task.Yield
方法
Task.Yield
方法创建一个立即返回的 awaitable
。等待一个 Yield
可以让异步方法在执行后续部分的同时返回到调用方法