目录
1.基本介绍
异步编程大概等同于多线程编程,一般而言多线程编程是比较复杂的,涉及到很多计算机底层的东西,不过C#在5.0后发不了await/async之后,异步编程编的简单许多。
要在C#中创建一个异步任务需要应用Task类,它在System.Threading.Tasks命名空间中:Task类继承于Object,并实现了IAsyncResult,和IDisposable接口。Task<TResult>类的表示单个操作通常返回一个值,以异步方式执行。 Task<TResult> 对象是一种的中心思想基于任务的异步模式首次引入.NET Framework 4 中。 因为由执行工作Task<TResult>对象通常以异步方式执行线程池线程上而不是以同步方式在主应用程序线程中,可以使用Status属性,并将IsCanceled, IsCompleted,和IsFaulted属性,以确定任务的状态。 大多数情况下,lambda 表达式用于指定该任务所执行的工作量。
2.创建一个任务:Task对象
如果一个程序调用一个方法,等待其执行所有处理后才继续执行,我们就称这个方法是同步的,这是默认的形式。相反,异步方法在处理完成结束前就返回到调用方法。C#中的异步方法主要又三部分组成:
- 调用方法:该方法调用异步方法,在异步方法执行时仍能继续执行。
- 异步(async)方法:该方法异步执行其工作,完成后立即返回到调用方法。
- await表达式:用于异步方法的内部,指明需要异步执行的任务。
结构示意图如下:
先看一个官方的例子:
using System;
using System.Threading;
using System.Threading.Tasks;
class Example
{
static void Main()
{
Action<object> action = (object obj) =>
{
Console.WriteLine("Task={0}, obj={1}, Thread={2}",
Task.CurrentId, obj,
Thread.CurrentThread.ManagedThreadId);
};
// Create a task but do not start it.
Task t1 = new Task(action, "alpha");
// Construct a started task
Task t2 = Task.Factory.StartNew(action, "beta");
// Block the main thread to demonstrate that t2 is executing
t2.Wait();
// Launch t1
t1.Start();
Console.WriteLine("t1 has been launched. (Main Thread={0})",
Thread.CurrentThread.ManagedThreadId);
// Wait for the task to finish.
t1.Wait();
// Construct a started task using Task.Run.
String taskData = "delta";
Task t3 = Task.Run( () => {Console.WriteLine("Task={0}, obj={1}, Thread={2}",
Task.CurrentId, taskData,
Thread.CurrentThread.ManagedThreadId);
});
// Wait for the task to finish.
t3.Wait();
// Construct an unstarted task
Task t4 = new Task(action, "gamma");
// Run it synchronously
t4.RunSynchronously();
// Although the task was run synchronously, it is a good practice
// to wait for it in the event exceptions were thrown by the task.
t4.Wait();
}
}
// The example displays output like the following:
// Task=1, obj=beta, Thread=3
// t1 has been launched. (Main Thread=1)
// Task=2, obj=alpha, Thread=4
// Task=3, obj=delta, Thread=3
// Task=4, obj=gamma, Thread=1
这个例子一共用构造了4个任务:
任务 | 方法原型 | 说明 |
t1 | public Task (Action<object> action, object state); | action表示要在任务中执行的代码委托,state表示由该操作使用的数据对象。 |
t2 | public System.Threading.Tasks.Task StartNew (Action<object> action, object state); | 同上 |
t3 | public static System.Threading.Tasks.Task Run (Action action); | Action表示要在任务中执行的代码的委托。将在线程池上运行的指定工作排队,并返回代表该工作的 Task 对象。 |
t4 | public Task (Action<object> action, object state); | 同t1 |
这里说一下t1和t2创建任务的区别:从开始.NET Framework 4.5,则Task.Run方法是启动计算密集型任务的建议的方法。 使用StartNew方法仅在需要精细地控制长时间运行的计算密集型任务时。
3.异步方法的返回值
在异步方法中的async:
- 方法头中必须包含async关键字,且必须出现在返回类型之前。
- 该修饰符只是包含一个或多个await表达式,也就是说它本身并不能创建任何异步操作。
- async关键字是一个上下文关键字,也就是说除了作为方法的修饰符之外,async还可以用作标识符。
对于异步方法的返回值,必须是一下三种情况之一:
- void
- Task
- Task<T>
如果调用方法要从调用中获取一个T类型数据,异步方法的返回类型就必须是Task<T>,调用方法通过读取Task的Result属性来获取这个T类型的值。一个简单的示例:
Task<int> value=DoStuff.CalculateSumAsync(5,6);
...
Console.WriteLine("Value:{0}",value.Result);
如果调用方法不需要重异步方法中返回某个值,但需要检查异步方法的状态,那么异步方法可以返回一个Task类型对象,。这时即使异步方法中出现了return语句,也不会返回任何东西。
如果调用方法仅仅想执行异步方法,而不需要做进一步的交互,异步方法可以返回void类型。
4.异步方法的控制流
异步方法的结构包含三个不同的区域,如下图所示:
- 第一个是await表达式之前的部分:从方法开头到第一个await表达式之间的所有代码,这一部分应该只包含少量且无需长时间处理的代码。
- await表达式:表示将要异步处理的任务
- 后续部分:在await表达式之后出现的方法中的其余代码。
下面是一个异步方法完整的执行流程:
5.await表达式
await表达式指定了一个异步任务。其语法如下:
await task
由await关键字和一个空闲对象组成,这个任务可能是一个Task类型的对象,也可能不是,默认情况下,这个任务在当前线程异步运行。
一个空闲对象即是一个awaitable实例。awaitable对象是指包含GetAwaiter方法的类型,该方法没有参数返回一个成为awaiter类型的对象。awaiter类型包含一下成员:
- bool IsCompleted {get;}
- void OnCompleted(Action);
- void GetResult();
- T GetResult(); (T为任意类型)
然而实际上我们不需要构建自己的awaitable类型。我们应该使用Task类,他是一个awaitable类型。对于awaitable,大多数情况下所需要的就是Task了。在NET4.5中微软发布了大量的和新修订的异步方法(在BCL中),他们可以返回Task<T>类型对象,将这些对象放到await表达式中,它们将在当前线程异步执行。例如Net类中的WebClient.DownloadStaringTaskAsync方法就是其中的一个。
Uri site=new Uri("http://www.CCTV.com.cn");
WebClient wc=new WebClient();
string Result=await wc.DownloadStringTaskAsync(site);
尽管目前BCL中存在很多返回Task<T>类型对象的方法,你仍然肯能要自己编写一些异步方法,作为await表达式的任务,最简单的方式是在你的方法使用Task.Run方法来创建一个Task。关于Task.Run,官方有详细的说明文档,其中有这么一句话:
Run方法提供了一组重载,可以轻松启动任务通过使用默认值。 它是轻量替代StartNew重载。
Task.Run是Task类的静态成员函数,将在线程池上运行的指定工作排队,并返回该工作的任务或 Task<TResult> 句柄。因此它是在不同的线程运行你的方法。例如Run的一个形式如下:
Task Run(Func<TReturn> func)
Func<TResult>是一个预定义的委托,它不包含任何参数,返回值类型为TReturn。下面给出一个完整的例子:
using System;
using System.Threading.Tasks;
namespace Task_Run
{
class MainClass
{
public static void Main(string[] args)
{
Task t = (new MyClass()).DoWorkAsync();
t.Wait();
}
}
class MyClass
{
public int Get10()
{
return 10;
}
public async Task DoWorkAsync()
{
Func<int> ten = new Func<int>(Get10);
int a = await Task.Run(ten);
int b = await Task.Run(new Func<int>(Get10));
int c = await Task.Run(() => { return 10; });
Console.WriteLine($"{a},{b},{c}");
}
}
}
这里做一个小节:
这里有一个新的类型:CancellationToken,是用来取消异步操作的,后面会讲到。