异步编程

从版本5.0开始,C#引入了async/await的异步编程模式,大大简化了异步编程的难度,使我们摆脱了回调函数、事件等传统的方式,可以使用类似同步编程的方式进行异步编程,这里通过一个小例子说明这种编程方式以及需要注意的地方。

异步编程的主要目的是当执行到耗时较多的过程时,先启动这个过程,然后继续执行后续代码,当需要这个过程的返回的值时,再进行等待。这些耗时的过程可能是从网上下载文件,或者从数据库读入初始化数据等等。使用async/await方式编程时,需要使用async声明需要异步执行的过程,比如:

1
2
3
4
5
6
7
8
9
async static Task<string> DoTask(int i)
{
Console.WriteLine("任务" + i+ "begin:" + Thread.CurrentThread.ManagedThreadId);
await Task.Run(() => Console.WriteLine("任务" + i+ ":" + Thread.CurrentThread.ManagedThreadId));
val = i.ToString();
Console.WriteLine("任务" +i+ "end:" + Thread.CurrentThread.ManagedThreadId);
return "任务"+i;

}

async返回的类型是Task或者Task<>,如果没有返回,可以是void。需要注意的是:async声明的函数或者方法中,必须有使用await语句执行的异步代码,否则这个函数或过程会被同步执行。反过来,使用await语句调用异步执行代码的函数,也必须声明为async。在同步函数中可以直接调用异步函数,使用Task.Result获取结果值,比如:

1
string res=DoTask(10).Result;

下面的代码启动若干异步任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ZL.AsyncDemo
{
class Program
{
static string val;
static void Main(string[] args)
{
Run();
Console.ReadLine();
}

async static void Run()
{
var tasks = new List<Task<string>>();
for(var i = 0; i < 5; i++)
{
var task = DoTask(i);
tasks.Add(task);
}

Console.WriteLine("任务Main:" + Thread.CurrentThread.ManagedThreadId);

while (tasks.Any())
{
Task<string> finished = await Task<string>.WhenAny(tasks);
Console.WriteLine(finished.Result);
tasks.Remove(finished);
}

Console.WriteLine(val);
}

async static Task<string> DoTask(int i)
{
Console.WriteLine("任务" + i+ "begin:" + Thread.CurrentThread.ManagedThreadId);
await Task.Run(() => Console.WriteLine("任务" + i+ ":" + Thread.CurrentThread.ManagedThreadId));
val = i.ToString();
Console.WriteLine("任务" +i+ "end:" + Thread.CurrentThread.ManagedThreadId);
return "任务"+i;

}
}
}

从执行结果看,这些任务不是在同一线程执行的:

异步编程下需要注意的是访问公用变量的问题,如果多个异步任务对同一公用变量进行修改的化,结果可能不可预测,我们在这里定义了一个简单的公用变量,在异步任务中对它进行赋值,最后的结果是不可预测的,上次执行时结果是4,这次执行就变成了2: