同步任务(一般指方法):一个应用程序调用某个方法,等到其执行完才进行下一步操作。
异步任务(一般指方法):一个程序调用某一方法,在处理完成前就返回该方法,不需要等待方法完成就可以继续执行后面的代码。
异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。
微软极力推荐基于Task任务的async和await关键字实现方法的异步调用,也称为基于任务的异步模式,简称TAP(Task-base asynchronous pattern)。C#5.0中的async和await关键字只是编译器功能,C#编译器在内部还是会使用Task类创建代码的。
方法上要有async关键字,放在访问修饰符后面。
方法名最好以Async结尾,表示这是一个异步方法,从方法名就能识别出来。
方法的返回值类型只能是3种:Task、Task< T >、void。
方法体代码中要包含1个或多个await表达式,表示需要异步执行的任务,如果方法体中不存在await表达式,则与普通方法一样执行。await后面跟一个Task.Run(…)或者直接调用其他的一个异步方法。
public async void Method1Async()
{
await Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine($"I am Method1Async {Thread.CurrentThread.ManagedThreadId}");
}
});
}
public async Task Method2Async()
{
await Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"I am Method2Async {Thread.CurrentThread.ManagedThreadId}");
}
});
}
public async Task<string> Method3Async()
{
string res = await Task<string>.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine($"I am Method3Async {Thread.CurrentThread.ManagedThreadId}");
}
return "I am Method3Async";
});
return res;
}
static void Main(string[] args)
{
Foo foo = new Foo();
Console.WriteLine($"begin Method1Async {Thread.CurrentThread.ManagedThreadId}");
foo.Method1Async();//执行到这句的时候不会阻塞,而是会继续执行下面的代码
Console.WriteLine($"end Method1Async {Thread.CurrentThread.ManagedThreadId}");
}
output:
begin Method1Async 1
I am Method1Async CurrentThread:4 0
I am Method1Async CurrentThread:4 1
I am Method1Async CurrentThread:4 2
I am Method1Async CurrentThread:4 3
I am Method1Async CurrentThread:4 4
I am Method1Async CurrentThread:4 5
I am Method1Async CurrentThread:4 6
I am Method1Async CurrentThread:4 7
I am Method1Async CurrentThread:4 8
I am Method1Async CurrentThread:4 9
end Method1Async 1
I am Method1Async CurrentThread:4 10
I am Method1Async CurrentThread:4 11
I am Method1Async CurrentThread:4 12
I am Method1Async CurrentThread:4 13
I am Method1Async CurrentThread:4 14
I am Method1Async CurrentThread:4 15
I am Method1Async CurrentThread:4 16
I am Method1Async CurrentThread:4 17
I am Method1Async CurrentThread:4 18
I am Method1Async CurrentThread:4 19
I am Method1Async CurrentThread:4 20
I am Method1Async CurrentThread:4 21
I am Method1Async CurrentThread:4 22
I am Method1Async CurrentThread:4 23
I am Method1Async CurrentThread:4 24
I am Method1Async CurrentThread:4 25
I am Method1Async CurrentThread:4 26
I am Method1Async CurrentThread:4 27
I am Method1Async CurrentThread:4 28
我们可以看到,异步方法中的打印内容还没有执行100次就结束了,这是因为在执行异步方法的时候不会阻塞,而是会继续执行异步方法后面的代码,所以我们看到“end Method1Async 1”是在中间打印的。这句话打印完了之后Main方法就执行完毕了,程序结束,所以最终异步方法没有执行完毕就终止了。建议在正式项目中不要使用void返回类型的异步方法。
static void Main(string[] args)
{
Foo foo = new Foo();
Console.WriteLine($"begin Method2Async {Thread.CurrentThread.ManagedThreadId}");
Task task = foo.Method2Async();//异步方法不会阻塞 会立刻执行下面的代码
Console.WriteLine($"Task Id:{task.Id} Task Status:{task.Status}");
task.Wait();//在这里会进行无限等待,但是主线程是释放的,也不会阻塞(控制台还可以移动),只到待异步方法的完成后继续执行下面的代码。
//task.Wait(1000);//只等待10000ms,即使异步方法没有结束也会继续执行下面的代码
Console.WriteLine($"Task Id:{task.Id} Task Status:{task.Status}");//可以看看异步方法结束后的状态
Console.WriteLine($"end Method2Async {Thread.CurrentThread.ManagedThreadId}");
}
output:
begin Method2Async 1
I am Method2Async CurrentThread:4 0
Task Id:2 Task Status:WaitingForActivation
I am Method2Async CurrentThread:4 1
I am Method2Async CurrentThread:4 2
I am Method2Async CurrentThread:4 3
I am Method2Async CurrentThread:4 4
I am Method2Async CurrentThread:4 5
I am Method2Async CurrentThread:4 6
I am Method2Async CurrentThread:4 7
I am Method2Async CurrentThread:4 8
I am Method2Async CurrentThread:4 9
Task Id:2 Task Status:RanToCompletion
end Method2Async 1
由于返回的是Task类型,所以我们可以获取与任务相关的信息(Status、Id等信息),甚至可以执行Wait方法、WaitAny和WaitAll等方法。
值得一提的是Wait()方法,如果需要等待异步方法执行完毕则需要调用Wait()方法。
static void Main(string[] args)
{
Foo foo = new Foo();
Console.WriteLine($"begin Method1Async {Thread.CurrentThread.ManagedThreadId}");
Task<string> task = foo.Method3Async();
Console.WriteLine(task.Result);//如果没有在异步方法没有完成的时候调用Result属性,那么在此处会自动Wait(),直到异步方法执行完成。
//task.Wait();//在这里会进行无限等待,但是主线程是释放的,也不会阻塞(控制台还可以移动),只到待异步方法的完成后继续执行下面的代码。
//task.Wait(1000);//只等待10000ms,即使异步方法没有结束也会继续执行下面的代码
Console.WriteLine($"Task Id:{task.Id} Task Status:{task.Status}");//可以看看异步方法结束后的状态
Console.WriteLine($"end Method3Async {Thread.CurrentThread.ManagedThreadId}");
}
Task中T类型就是Result属性的类型,也就是异步方法中return的类型。如果在异步方法执行过程中调用Result属性,那么就相当于调用了Wait()方法,再调用Result属性得到异步方法的返回结果。
值得一提的是Task类继承自Task类型,所以T在ask类中的属性和方法,Task对象也可以直接调用。
要回答上述问题,我们将第二个异步方法修改为如下:
public async Task Method2Async()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"before TaskRun CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
}
await Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"I am Method2Async CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
}
});
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"after TaskRun CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
}
}
在Main方法中执行如下:
static void Main(string[] args)
{
Foo foo = new Foo();
Console.WriteLine($"begin Method2Async CurrentThread:{Thread.CurrentThread.ManagedThreadId}");
Task task = foo.Method2Async();//异步方法不会阻塞 会立刻执行下面的代码
Console.WriteLine($"Task Id:{task.Id} Task Status:{task.Status}");
task.Wait();//在这里会进行无限等待,但是主线程是释放的,也不会阻塞(控制台还可以移动),只到待异步方法的完成后继续执行下面的代码。
//task.Wait(1000);//只等待10000ms,即使异步方法没有结束也会继续执行下面的代码
Console.WriteLine($"Task Id:{task.Id} Task Status:{task.Status}");//可以看看异步方法结束后的状态
Console.WriteLine($"end Method2Async CurrentThread:{Thread.CurrentThread.ManagedThreadId}");
}
打印结果为:
output:
begin Method2Async CurrentThread:1
before TaskRun CurrentThread:1 0
before TaskRun CurrentThread:1 1
before TaskRun CurrentThread:1 2
before TaskRun CurrentThread:1 3
before TaskRun CurrentThread:1 4
before TaskRun CurrentThread:1 5
before TaskRun CurrentThread:1 6
before TaskRun CurrentThread:1 7
before TaskRun CurrentThread:1 8
before TaskRun CurrentThread:1 9
Task Id:2 Task Status:WaitingForActivation
I am Method2Async CurrentThread:4 0
I am Method2Async CurrentThread:4 1
I am Method2Async CurrentThread:4 2
I am Method2Async CurrentThread:4 3
I am Method2Async CurrentThread:4 4
I am Method2Async CurrentThread:4 5
I am Method2Async CurrentThread:4 6
I am Method2Async CurrentThread:4 7
I am Method2Async CurrentThread:4 8
I am Method2Async CurrentThread:4 9
after TaskRun CurrentThread:4 0
after TaskRun CurrentThread:4 1
after TaskRun CurrentThread:4 2
after TaskRun CurrentThread:4 3
after TaskRun CurrentThread:4 4
after TaskRun CurrentThread:4 5
after TaskRun CurrentThread:4 6
after TaskRun CurrentThread:4 7
after TaskRun CurrentThread:4 8
after TaskRun CurrentThread:4 9
Task Id:2 Task Status:RanToCompletion
end Method2Async CurrentThread:1
我们可以看到,在异步方法中:await之前的代码块与调用线程相同的,await中的代码块是开启了一个任务,会在线程池中去取一个线程执行这个任务,但是await之后的代码块是线程池中的线程相同的,并不是调用线程。。。。
在遇到awiat关键字之前,程序是按照代码顺序自上而下以同步方式执行的。
在遇到await关键字之后,系统做了以下工作:
所以,在调用:
Task task = foo.Method2Async();
后,并不会立刻执行下面的代码块,而是进入异步方法中,执行await表达式之前的代码块(所以我们看到执行这一块代码的线程与调用线程相同),直到遇到await表达式后,异步方法将被挂起,才会将控制权返回给调用线程,继续进行调用线程中的代码。同时,会使用线程池中的线程(并不是创建新的线程)来执行await表达式,所以不会造成程序的阻塞。
当该线程执行完await表达式后,若后面还有代码,则由该线程继续执行。(所以我们看到执行这一块的线程与await表达式中的线程相同)
所以,很多地方可以看到以下的写法:
public async Task Method2Async()
{
await Task.Yield();//可等待的空任务,所以await后的语句,都是线程池中调度的线程执行的,并不是调用异步方法的线程执行的。
//就是一个切换线程的操作。。。
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Method2Async Run CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
}
}
就是通过await Task.Yield()
来切换线程。
同步和异步关注的是消息通信机制。所谓同步,就是在发出一个调用后主动等待这个调用的结果。
而异步则相反,调用在发出之后,不会立刻的到结果,而是在调用发出后,被调用者通过状态、通知来通知调用者,或者通过回调函数处理这个调用。
阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。