.net core 非阻塞的异步编程 及 线程调度过程
简介:
【11月更文挑战第12天】本文介绍了.NET Core中的非阻塞异步编程,包括其基本概念、实现方式及应用示例。通过`async`和`await`关键字,程序可在等待I/O操作时保持线程不被阻塞,提高性能。文章还详细说明了异步方法的基础示例、线程调度过程、延续任务机制、同步上下文的作用以及如何使用`Task.WhenAll`和`Task.WhenAny`处理多个异步任务的并发执行。
- 非阻塞异步编程概述
- 在.NET Core 中,非阻塞异步编程是一种高效的编程模式,它允许程序在等待某些操作(如 I/O 操作,像读取文件、网络请求等)完成时不会阻塞线程,从而可以充分利用系统资源,提高应用程序的性能和响应能力。
- 主要通过
async
和await
关键字来实现。async
用于标记一个方法是异步方法,await
用于暂停异步方法的执行,直到等待的操作完成。
- 异步方法基础示例
- 以下是一个简单的异步方法示例,用于模拟一个异步操作(比如读取文件或网络请求):
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Before asynchronous operation");
await DoSomethingAsync();
Console.WriteLine("After asynchronous operation");
}
static async Task DoSomethingAsync()
{
await Task.Delay(1000); // 模拟一个耗时1秒的操作
}
}
Main
方法被标记为async
,表示它是一个异步方法。
await DoSomethingAsync()
暂停Main
方法的执行,直到DoSomethingAsync
中的Task.Delay
操作完成。
DoSomethingAsync
方法也是异步的,它通过await Task.Delay(1000)
模拟一个耗时 1 秒的操作。
- 线程调度过程 - 任务和线程池的关系
- 当一个异步方法被调用时,它通常会返回一个
Task
或Task<T>
(其中T
是返回值类型)。这些任务会被放入线程池(ThreadPool)中进行调度。
- 线程池是一组预先创建好的线程,用于执行任务。当一个
await
操作发生时,当前线程可能会被释放回线程池,去执行其他任务。
- 例如,在上面的
DoSomethingAsync
方法中,当执行到await Task.Delay(1000)
时,当前执行的线程可以去处理其他任务。当Task.Delay
完成后,线程池会调度一个线程(可能是之前的线程,也可能是其他线程)来继续执行DoSomethingAsync
方法的后续代码。
- 继续执行的机制 - 延续任务(Continuation)
- 当一个
await
操作完成后,后续的代码会作为一个延续任务被安排执行。延续任务是一种特殊的任务,它依赖于之前的任务完成。
- 例如,在
Main
方法中,await DoSomethingAsync()
后的Console.WriteLine("After asynchronous operation");
就是一个延续任务。当DoSomethingAsync
中的操作完成后,线程池会调度一个线程来执行这个延续任务。
- 这种机制确保了异步操作完成后的正确执行顺序,同时避免了不必要的线程阻塞。
- 异步编程中的同步上下文(Synchronization Context)
- 在某些情况下,如在 Windows Forms 或ASP.NET应用程序中,存在同步上下文。同步上下文用于确保在正确的线程上执行代码,以避免线程安全问题。
- 例如,在 Windows Forms 应用程序中,UI 控件只能在创建它们的线程(通常是 UI 线程)上进行更新。当在异步方法中更新 UI 时,
await
操作会捕获当前的同步上下文,并在合适的线程(如 UI 线程)上执行延续任务。
- 不过,在控制台应用程序(如前面的示例)中,通常没有同步上下文,所以延续任务可以在任何线程池线程上执行。
- 复杂场景下的线程调度 - 多个异步任务并发执行
- 可以使用
Task.WhenAll
或Task.WhenAny
来处理多个异步任务的并发执行。
Task.WhenAll
会等待所有指定的任务都完成,例如:
static async Task Main()
{
var task1 = DoSomethingAsync();
var task2 = DoSomethingElseAsync();
await Task.WhenAll(task1, task2);
Console.WriteLine("Both tasks are completed");
}
- 在这个示例中,
task1
和task2
是两个异步任务,Task.WhenAll
会使Main
方法暂停执行,直到task1
和task2
都完成。在等待过程中,线程池会有效地调度线程来执行这些任务,提高了并发性能。
Task.WhenAny
则是等待其中一个任务完成,一旦有一个任务完成,就会继续执行后续代码。这在处理多个可能有不同响应时间的任务时非常有用,比如多个网络请求,只要有一个请求返回结果就可以进行下一步操作。