一. 同步
简单地说,就是程序严格按照代码的逻辑顺序,一行一行执行。
看下面代码:
static void Main(string[] args)
{
Console.WriteLine("Syc - Start");
Console.WriteLine();
Console.WriteLine("enter func1");
func1();
Console.WriteLine("out func1");
Console.WriteLine();
Console.WriteLine("enter func2");
func2();
Console.WriteLine("out func2");
Console.WriteLine();
Console.WriteLine("enter func3");
func3();
Console.WriteLine("out func3");
Console.WriteLine();
Console.WriteLine("Syc - Done");
}
private static void func1()
{
Console.WriteLine("func1 Start");
Thread.Sleep(1000);
Console.WriteLine("func1 End");
}
private static void func2()
{
Console.WriteLine("func2 Start");
Thread.Sleep(3000);
Console.WriteLine("func2 End");
}
private static void func3()
{
Console.WriteLine("func3 Start");
Thread.Sleep(5000);
Console.WriteLine("func3 End");
}
顺序执行结果:
Syc - Start
enter func1
func1 Start
func1 End
out func1
enter func2
func2 Start
func2 End
out func2
enter func3
func3 Start
func3 End
out func3
Syc - Done
如上,程序按照逻辑顺序执行,没有任何错误和意外。
那么异步呢?下面看看异步编程。
二. 异步
C#中,一个核心,两个关键字。
一个核心是指Task和Task对象,而两个关键字就是async和await。
异步编程的方式:
async Task function()
{
/* your code here */
}
然后调用它:
await function();
看代码:
static async Task Main(string[] args)
{
Console.WriteLine("Syc - Start");
Console.WriteLine();
Console.WriteLine("enter func1");
await Func1();
Console.WriteLine("out func1");
Console.WriteLine();
Console.WriteLine("enter func2");
await Func2();
Console.WriteLine("out func2");
Console.WriteLine();
Console.WriteLine("enter func3");
await Func3();
Console.WriteLine("out func3");
Console.WriteLine();
Console.WriteLine("Syc - Done");
}
private static async Task Func1()
{
Console.WriteLine("func1 Start");
Thread.Sleep(1000);
Console.WriteLine("func1 End");
}
private static async Task Func2()
{
Console.WriteLine("func2 Start");
Thread.Sleep(3000);
Console.WriteLine("func2 End");
}
private static async Task Func3()
{
Console.WriteLine("func3 Start");
Thread.Sleep(5000);
Console.WriteLine("func3 End");
}
结果:
Syc - Start
enter func1
func1 Start
func1 End
out func1
enter func2
func2 Start
func2 End
out func2
enter func3
func3 Start
func3 End
out func3
Syc - Done
誒?好像和上面同步代码结果没什么不一样。
没错,上面这个代码就是同步执行的。
这是异步编程的一个容易错误的理解:async和await的配对。
三. async和await的配对
注:
在异步编程的规范中,async修饰的方法,仅仅表示这个方法在内部有可 能采用异步的方式执行,CPU在执行这个方法时,会放到一个新的线程中执行。
那这个方法,最终是否采用异步执行,不决定于是否用await方式调用这个方法,而决定于这个方法内部,是否有await方式的调用。
看代码很容易理解:
private static async Task func1()
{
Console.WriteLine("Func1 proccess - start");
Thread.Sleep(1000);
Console.WriteLine("Func1 proccess - end");
}
这个方法,因为内部没有await调用,不管你调用时候有没有await 这个方法都是按照同步方式执行。
而下面这个代码:
private static async Task func1()
{
Console.WriteLine("Func1 proccess - start");
await Task.Run(() => Thread.Sleep(1000));
Console.WriteLine("Func1 proccess - end");
}
因为这个方法内部有await调用,所以不管你用什么方式调用,都是异步执行。
看代码:
static async Task Main(string[] args)
{
Console.WriteLine("Async proccess - start");
Console.WriteLine("Async proccess - enter Func1");
func1();
Console.WriteLine("Async proccess - out Func1");
Console.WriteLine("Async proccess - enter Func2");
func2();
Console.WriteLine("Async proccess - out Func2");
Console.WriteLine("Async proccess - enter Func3");
func3();
Console.WriteLine("Async proccess - out Func3");
Console.WriteLine("Async proccess - done");
Console.WriteLine("Main proccess - done");
Console.ReadKey();
}
private static async Task func1()
{
Console.WriteLine("Func1 proccess - start");
await Task.Run(() => Thread.Sleep(1000));
Console.WriteLine("Func1 proccess - end");
}
private static async Task func2()
{
Console.WriteLine("Func2 proccess - start");
await Task.Run(() => Thread.Sleep(3000));
Console.WriteLine("Func2 proccess - end");
}
private static async Task func3()
{
Console.WriteLine("Func3 proccess - start");
await Task.Run(() => Thread.Sleep(5000));
Console.WriteLine("Func3 proccess - end");
}
输出结果:
Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Async proccess - out Func3
Async proccess - done
Main proccess - done
Func1 proccess - end
Func2 proccess - end
Func3 proccess - end
在结果中,在长时间运行Thread.Sleep的时候,跳出去往下执行了,是异步。
那么异步调用不是要用await吗?
我们把await加到调用方法前面,试一下:
结果:
Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Async proccess - out Func3
Async proccess - done
Main proccess - done
欸?what aver? wo靠?怎么好像又是同步了?
这是另一个容易错误的理解:await是什么意思?
四. await什么意思
提到await,就得先说说Wait。
字面意思,Wait就是等待。
前边说了,异步有一个核心,是Task。而Task有一个方法,就是Wait,写法是Task.Wait()。所以,很多人把这个Wait和await混为一谈,这是错的。
这个问题来自于Task。C#里,Task不是专为异步准备的,它表达的是一个线程,是工作在线程池里的一个线程。异步是线程的一种应用,多线程也是线程的一种应用。Wait,以及Status、IsCanceled、IsCompleted、IsFaulted等等,是给多线程准备的方法,跟异步没有半毛钱关系。当然你非要在异步中使用多线程的Wait或其它,从代码编译层面不会出错,但程序会。
尤其,Task.Wait()是一个同步方法,用于多线程中阻塞等待。
在那个「同步方法中调用异步方法」的文章中,用Task.Wait()来实现同步方法中调用异步方法,这个用法本身就是错误的。 异步不是多线程,而且在多线程中,多个Task.Wait()使用也会死锁,也有解决和避免死锁的一整套方式。
再说一遍:Task.Wait()是一个同步方法,用于多线程中阻塞等待,不是实现同步方法中调用异步方法的实现方式。
说回await。字面意思,也好像是等待。是真的吗?
并不是,await不完全是等待的意思。
在异步中,await表达的意思是:当前线程/方法中,await引导的方法出结果前,跳出当前线程/方法,从调用当前线程/方法的位置,去执行其它可能执行的线程/方法,并在引导的方法出结果后,把运行点拉回到当前位置继续执行;直到遇到下一个await,或线程/方法完成返回,跳回去刚才外部最后执行的位置继续执行。
看代码:
static async Task Main(string[] args)
{
1 Console.WriteLine("Async proccess - start");
2 Console.WriteLine("Async proccess - enter Func1");
3 func1();
4 Console.WriteLine("Async proccess - out Func1");
5 Console.WriteLine("Async proccess - done");
6 Thread.Sleep(2000);
7 Console.WriteLine("Main proccess - done");
8 Console.ReadKey();
}
private static async Task func1()
{
9 Console.WriteLine("Func1 proccess - start");
10 await Task.Run(() => Thread.Sleep(1000));
11 Console.WriteLine("Func1 proccess - end");
}
这个代码,执行时是这样的:顺序执行1、2、3,进到func1,执行
9、10,到10时,有await,所以跳出,执行4、5、6。而6是一个长
时等待,在等待的过程中,func1的10运行完成,运行点跳回10,
执行11并结束方法,再回到6等待,结束等待后继续执行7、8结束
看一下结果:
Async proccess - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - done
Func1 proccess - end
Main proccess - done
在这个例子中,await在控制异步的执行次序。那为什么要用等待这么个词呢?是因为await确实有等待结果的含义。
五. await的第二层意思 :等待拿到结果
看代码:
tatic async Task Main(string[] args)
{
Console.WriteLine("Async proccess - start");
Console.WriteLine("Async proccess - enter Func1");
Task<int> f = func1();
Console.WriteLine("Async proccess - out Func1");
Console.WriteLine("Async proccess - done");
int result = await f;
Console.WriteLine("Main proccess - done");
Console.ReadKey();
}
private static async Task<int> func1()
{
Console.WriteLine("Func1 proccess - start");
await Task.Run(() => Thread.Sleep(1000));
Console.WriteLine("Func1 proccess - end");
return 5;
}
比较一下这段代码和上一节的代码,很容易搞清楚执行过程。
这个代码,完成了这样一个需求:我们需要使用func1方法的返回值。我们可以提前去执行这个方法,而不急于拿到方法的返回值,直到我们需要使用时,再用await去获取到这个返回值去使用。
这才是异步对于我们真正的用处。对于一些耗时的IO或类似的操作,我们可以提前调用,让程序可以利用执行过程中的空闲时间来完成这个操作。等到我们需要这个操作的结果用于后续的执行时,我们await这个结果。这时候,如果await的方法已经执行完成,那我们可以马上得到结果;如果没有完成,则程序将继续执行这个方法直到得到结果。