这篇文章的目的并不是系统地介绍C#中的await
、async
关键字,而是针对我遇到的一些问题进行记录。
背景
await / async
C#中可以用async
标识方法,表示这个方法是异步的。异步方法的返回值必须是void
、Task
或者Task<T>
。例如:
public static async Task<int> Method(int i)
{
await Task.Delay(1000);
return i;
}
- 1
- 2
- 3
- 4
- 5
用async
修饰的lambda表达式
我们可以用async
修饰lambda表达式,标识这个表达式为异步。
Func<Task<HttpResponseMessage>> lambda = async () =>
{
using (var httpClient = new HttpClient())
{
var response = await httpClient.GetAsync("https://www.bing.com/");
return response;
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以看到,用async
修饰以后,返回值也变为Task<HttpResponseMessage>
。
async
标记只是针对方法内部,外部只关心返回值类型。
异步方法返回的时间
异步方法在遇到await
关键字之后就会返回。例如下面的代码:
private static Stopwatch stopwatch;
static void Main(string[] args)
{
stopwatch = Stopwatch.StartNew();
Task task = MethodAsync();
Console.WriteLine($"Main: {stopwatch.ElapsedMilliseconds}");
}
public static async Task MethodAsync()
{
Console.WriteLine("Enter method.");
Task.Delay(500).Wait();
Console.WriteLine($"Method will return: {stopwatch.ElapsedMilliseconds}");
await Task.Delay(1000);
Console.WriteLine($"End of method: {stopwatch.ElapsedMilliseconds}");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
MethodAsync()
方法会在遇到await
时返回。于是就有了如下输出:
Enter method.
Method will return: 542
Main: 544
- 1
- 2
- 3
Task
类的一些方法
Task.Run(Action action)
我们可以用Task.Run(Action action)
来启动一个任务。
static void Main(string[] args)
{
stopwatch = Stopwatch.StartNew();
var task = Task.Run(MethodAsync);
task.Wait();
Console.WriteLine($"Main: {stopwatch.ElapsedMilliseconds}");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
其中MethodAsync
就是上面定义的方法。
可以推测,MethodAsync
执行到第一个await
就会返回。Wait()
方法也会执行完毕。整段程序执行500多毫秒就应该结束。
让我们运行一下。
Enter method.
Method will return: 544
End of method: 1546
Main: 1547
- 1
- 2
- 3
- 4
奇怪,MethodAsync
返回不久,整个程序就应该结束才是,为什么等待了一秒多,直到MethodAsync
全部执行完?
Task.Run(Func<Task> function)
原来,Task.Run
那里实际调用的是Task.Run(Func<Task> function)
这个重载。这个重载返回的Task
对象,我们在调用其Wait()
成员方法时会等待function
返回的Task
对象执行结束。我认为应该是为了方便这种情况。
await Task.Run(async () =>
{
using (var httpClient = new HttpClient())
{
var response = await httpClient.GetAsync("https://www.bing.com/");
return response;
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在这段代码中,正是由于调用的是Task.Run(Func<Task> function)
这个重载,才保证了请求完成后才会继续执行后面的代码。
System.Threading.Tasks.Parallel
类的情况
System.Threading.Tasks.Parallel
类提供了非常方便的并发执行循环的方法。
Parallel.For(0, 10, i => Console.WriteLine(i));
- 1
输出
0
2
6
4
5
8
9
7
3
1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
再试试
Parallel.For(0, 10, async i =>
{
await Task.Delay(1000);
Console.WriteLine(i);
});
- 1
- 2
- 3
- 4
- 5
输出
- 1
是的,输出没有任何内容。
原因是,Parallel
和Task
不一样,它并不会等待返回的Task
,而是在方法返回后就结束。
所以,如果想确保异步方法完全完成,必须改为同步。
Parallel.For(0, 10, i =>
{
Task.Delay(1000).Wait();
Console.WriteLine(i);
});
- 1
- 2
- 3
- 4
- 5
输出
8
4
6
2
0
1
7
5
3
9