本篇博客来谈一下我对c#中的async和awaite关键字的理解。先来聊聊我在理解这个异步编程机制时的困惑吧。
我看了使用 Async 和 Await 的异步编程(C# 和 Visual Basic)这篇文章后,感觉so easy,异步方法返回一个Task<TResult>
对象,凭着我对Task类的“深入理解”,我就断定:当调用一个同步方法时,由于同步方法返回的是Task,.net自动就让这个task开始在后台线程中运行,然后这个同步方法直接返回,当调用await时,就等于调用了task的Wait()方法,会阻塞当前线程等待task返回结果。然后凭着我的理解,就写出了下面的代码
class Program
{
static void Main()
{
MyAsyncMethod();
Console.ReadKey();
}
static async void MyAsyncMethod()
{
Console.WriteLine("call MyAsyncMethod.....");
Task<int> calculateResult = CalculateAsync();
Console.WriteLine("MyAsyncMethod was called.......");
int result = await calculateResult;
Console.WriteLine($"result is {result}");
}
static async Task<int> CalculateAsync()
{
int count = 5;
for (int i = 0; i < 5; i++)
{
Thread.Sleep(5000);
Console.WriteLine($"calculating step {i + 1}");
}
Random random = new Random();
return random.Next();
}
}
按照我上面的理解,第11行和第13行会被连续输出出来,但实际的输出确实
call MyAsyncMethod.....
calculating step 1
calculating step 2
calculating step 3
calculating step 4
calculating step 5
MyAsyncMethod was called.......
result is 900866582
也就是说,直到CalculateAsync()
返回后,第13行代码才被执行!不是说好了使用async就是异步编程吗?这明明是逐步执行的!微软大骗子!
再看看文档:
异步方法旨在成为非阻止操作。 异步方法中的 await 表达式在等待的任务正在运行时不会阻止当前线程。 相反,表达式在继续时注册方法的其余部分并将控件返回到异步方法的调用方。
async 和 await 关键字不会导致创建其他线程。 因为异步方法不会在其自身线程上运行,因此它不需要多线程。 只有当方法处于活动状态时,该方法将在当前同步上下文中运行并使用线程上的时间。 可以使用 Task.Run 将占用大量 CPU 的工作移到后台线程,但是后台线程不会帮助正在等待结果的进程变为可用状态。
对于异步编程而言,该基于异步的方法优于几乎每个用例中的现有方法。 具体而言,此方法比 BackgroundWorker 更适用于 IO 绑定的操作,因为此代码更简单且无需防止争用条件。 结合 Task.Run 使用时,异步编程比 BackgroundWorker 更适用于 CPU 绑定的操作,因为异步编程将运行代码的协调细节与 Task.Run 传输至线程池的工作区分开来。
好像看明白了些什么。async和await不会创建其它线程,异步方法需要将耗时的工作放到Task.Run中执行。好吧,修改下代码
class Program
{
static void Main()
{
MyAsyncMethod();
string input = "";
while ((input = Console.ReadLine()) != "q")
{
Console.WriteLine($"your input is {input}");
}
}
static async void MyAsyncMethod()
{
Console.WriteLine("call MyAsyncMethod.....");
Task<int> calculateResult = CalculateAsync();
Console.WriteLine("MyAsyncMethod was called.......");
int result = await calculateResult;
Console.WriteLine($"result is {result}");
}
static async Task<int> CalculateAsync()
{
Console.WriteLine("calculation begin.....");
int t = await Task.Run(() => Calculate());
Console.WriteLine("calculation complete.....");
return t;
}
static int Calculate()
{
// Compute total count of digits in strings.
int count = 5;
for (int i = 0; i < 5; i++)
{
Thread.Sleep(5000);
Console.WriteLine($"calculating step {i + 1}");
}
Random random = new Random();
return random.Next();
}
}
这段代码的运行结果是:
call MyAsyncMethod.....
calculation begin.....
MyAsyncMethod was called.......
calculating step 1
calculating step 2
calculating step 3
calculating step 4
calculating step 5
calculation complete.....
result is 1534528428
计算的过程中还可以相应用户输入,比如:
call MyAsyncMethod.....
calculation begin.....
MyAsyncMethod was called.......
calculating step 1
hello
your input is hello
calculating step 2
下班了
your input is 下班了
calculating step 3
calculating step 4
world
your input is world
calculating step 5
calculation complete.....
result is 1805505488
上面的代码其实应该从下往上看。首先Calculate()
是一个耗时的任务,CalculateAsync()
是一个异步方法,当然,还是要使用Task.Run()
的。MyAsyncMethod()
方法来调用CalculateAsync()
方法。在Main
方法中,调用MyAsyncMethod()
时会直接返回,不阻塞当前线程,因为当前线程还需要接受用户的输入。
最后,我把async和await理解为一种更方便地进行异步编程的机制,本身并不创建线程,但却实现了异步,非常简单。但是,有异步必然有多线程,一个线程不可能同时运行两个方法,只是我们以后在编写类的时候,可以通过Task.Run()
来自己实现一些异步的方法,定义为async,这样可以简化UI线程的编程。