什么是异步
启动程序时,系统会在内存中创建一个新的进程。进程是构成运行程序的资源的集合。这些资源包括虚地址空间、文件句柄和许多其他程序运行所需的东西。
在进程内部,系统创建了一个称为线程的内核(kernel)对象,它代表了真正执行的程序。(线程是“执行线程”的简称。)一旦进程建立,系统会在Nain方法的第一行语句处就开始线程的执行
关于线程,需要了解以下知识点:
- 默认情况下,一个进程只包含一个线程,从程序的开始一直执行到结束
- 线程可以派生其他线程,因此在任意时刻,一个进程都可能包含不同状态的多个线程,来执行程序的不同部分
- 如果一个进程拥有多个线程,它们将共享进程的资源
- 系统为处理器执行所规划的单元是线程,不是进程
在异步程序中,程序代码不需要按照编写时的顺序严格执行。有时需要在一个新的线程中运行一部分代码,有时无需创建新的线程,但为了更好地利用单个线程的能力,需要改变代码的执行顺序
- 在C#5.0中引入的一个用来构建异步方法的新特性——async/await
- .NET框架的特性——BackgroundWorker类 和 .NET任务并行库(没有嵌入C#中)
async/await特性的结构
如果一个程序调用某个方法,等待其执行所有处理后才继续执行,我们就称这样的方法是同步的。这是默认的形式
相反,异步的方法在处理完成之前就返回到调用方法。C#的async/await特性可以创建并使用异步方法。该特性由三个部分组成
- 调用方法(calling method):该方法调用异步方法,然后在异步方法(可能在相同的线程,也可能在不同的线程)执行其任务的时候继续执行
- 异步(async)方法:该方法异步执行其工作,然后立即返回到调用方法
- await表达式:用于异步方法内部,指明需要异步执行的任务。一个异步方法可以包含任意多个await表达式,不过如果一个都不包含的话编译器会发出警告
什么是异步方法
在语法上,异步方法具有如下特征:
- 方法头中包含async方法修饰符
- 包含一个或多个await表达式,表示可以异步完成的任务
- 必须具备以下三种返回类型。第二种(Task)和第三种(Task)的返回对象表示将在未来完成的工作,调用方法和异步方法可以继续执行
■void
■Task
■Task<T>
- 异步方法的参数可以为任意类型任意数量,但不能为
out
或ref
参数 - 按照约定,异步方法的名称应该以
Async
为后缀 - 除了方法以外,
Lambda
表达式和匿名方法也可以作为异步对象
注意:
- 异步方法在方法头中必须包含
async
关键字,且必须出现在返回类型之前 - 该修饰符只是标识该方法包含一个或多个
await
表达式。也就是说,它本身并不能创建任何异步操作 - async关键字是一个上下文关键字,也就是说除了作为方法修饰符(或Lambda表达式修饰符、匿名方法修饰符)之外,async还可用作标识符
异步方法实例:(异步方法三种返回类型)
//使用返回 Task<T> 对象的异步方法
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Task<int> value = DoAsyncStuff.CalculateSumAsync( 5, 6 );
// 处理其他事情 ...
Console.WriteLine( "Value: {0} ", value.Result );
}
}
static class DoAsyncStuff
{
public static async Task<int> CalculateSumAsync( int i1, int i2 )
{
int sum = await Task.Run(() => GetSum( i1, i2 ));
return sum;
}
private static int GetSum( int i1, int i2 )
{
return i1 + i2;
}
}
//使用返回 Task 对象的异步方法
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Task someTask = DoAsyncStuff.CalculateSumAsync(5, 6);
// 处理其他事情
someTask.Wait();
Console.WriteLine( "Async stuff is done" );
}
}
static class DoAsyncStuff
{
public static async Task CalculateSumAsync( int i1, int i2)
{
int value = await Task.Run(() => GetSum( i1, i2 ));
Console.MriteLine("Value: {0}", value );
}
private static int GetSum( int i1, int i2)
{
return i1 + i2;
}
}
//使用返回 "调用并忘记(void)" 的异步方法
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
DoAsyncStuff.CalculateSumAsync(5, 6);
// 处理其他事情
Thread.Sleep( 200 );
Console.WriteLine( "Program Exiting" );
}
}
static class DoAsyncStuff
{
public static async void CalculateSumAsync(int i1, int i2)
{
int value = await Task.Run(() => GetSum( i1, i2 ));
Console.MriteLine( "Value:(0) ", value );
}
private static int GetSum(int i1, int i2)
{
return i1 + i2;
}
)
异步方法的控制流
- 第一个await表达式之前的部分:从方法开头到第一个await表达式之间的所有代码。这一部分应该只包含少量且无需长时间处理的代码
- await表达式:表示将被异步执行的任务
- 后续部分:在await表达式之后出现的方法中的其余代码。包括其执行环境,如所在线程信息、目前作用域内的变量值,以及当await表达式完成后要重新执行所需的其他信息
它从第一个await表达式之前的代码开始,正常执行(同步地)直到遇见第一个await。这一区域实际上在第一个await表达式处结束,此时await任务还没有完成(大多数情况下如此)。当await任务完成时,方法将继续同步执行。如果还有其他await,就重复上述过程
当达到await表达式时,异步方法将控制返回到调用方法。如果方法的返回类型为Task或Task<T>
类型,将创建一个Task对象,表示需异步完成的任务和后续,然后将该Task返回到调用方法
目前有两个控制流:异步方法内的和调用方法内的。异步方法内的代码完成以下工作
- 异步执行await表达式的空闲任务。
- 当await表达式完成时,执行后续部分。后续部分本身也可能包含其他await表达式,这些表达式也将按照相同的方式处理,即异步执行await表达式,然后执行后续部分
- 当后续部分遇到
return
语句或到达方法末尾时,将:- 如果方法返回类型为
void
,控制流将退出 - 如果方法返回类型为
Task
,后续部分设置Task
的属性并退出。如果返回类型为Task<T>
,后续部分还将设置Task
对象的Result
属性
- 如果方法返回类型为
同时,调用方法中的代码将继续其进程,从异步方法获取Task对象。当需要其实际值时,就引用Task对象的Result属性。届时,如果异步方法设置了该属性,调用方法就能获得该值并继续。否则,将暂停并等待该属性被设置,然后再继续执行
await 表达式
await表达式指定了一个异步执行的任务。其语法如下所示,由await关键字和一个空闲对象(称为任务)组成。这个任务可能是一个Task类型的对象,也可能不是。默认情况下,这个任务在当前线程异步运行
取消一个异步操作
一些.NET异步方法允许你请求终止执行。同样也可以在自己的异步方法中加入这些特性
System.Threading.Tasks
命名空间中有两个类是为此目的而设计的:CancellationToken
和CancellationTokenSource
CancellationToken
对象包含一个任务是否应被取消的信息- 拥有
CancellationToken
对象的任务需要定期检查其令牌(token)状态。如果CancellationToken
对象的IsCancellationRequested
属性为true
,任务需停止其操作并返回 CancellationToken
是不可逆的,并且只能使用一次。也就是说,一旦IsCancellationRequested
属性被设置为true
,就不能更改了CancellationTokenSource
对象创建可分配给不同任务的CancellationToken
对象。任何持有CancellationTokenSource
的对象都可以调用其Cancel
方法,这会将CancellationToken
的IsCancellationRequested
属性设置为true
GUI程序中的异步操作
异步编程对于GUI程序非常重要,篇幅限制,不再赘述
使用异步的Lambda表达式
这里使用 WPF 程序来演示异步的Lambda表达式的用法
//前端界面xaml代码
<Window x:Class="AsyncLambda.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Async Lambda" Height="115" Width="150">
<StackPanel>
<TextBlock Name="workStartedTextBlock" Margin="10,10"/>
<Button Name="startWorkButton" Width="100" Margin="4" Content="Start Work"/>
</StackPanel>
</Window>
// 后台代码
using System.Threading.Tasks;
using System.Windows;
namespace AsyncLambda
{
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent();
// 异步Lambda表达式
startHorkButton.Click += async ( sender, e)=>
{
SetGuiValues( false, "Work Started");
await DoSomeWork();
SetGuiValues( true, "Hork Finished");
};
}
private void SetGuiValues(bool buttonEnabled, string status)
{
starthorkButton.IsEnabled = buttonEnabled;
workStartedTextBlock.Text = status;
}
private Task DoSomework()
{
return Task.Delay( 2500 );
}
}
}
ps:
就此结束