.NET教程 - 进程 & 异步 & 并行 第二
基于Task的异步模式(TAP,Task-Based Asynchronous Pattern)
说明#
处理异步工作,任务提供了比线程更好的抽象,任务会自动调度恰当数量的线程。但使用任务抽象的缺点是它颠倒了程序逻辑。为了解决这个问题出现了基于任务的异步模式/
注意:从C# 5.0开始才支持基于Task
的异步模式。
基于任务的异步模式可以轻松的将同步的程序转为异步程序,底层由C#进行自动转换,将异步方法转为任务延续。
基于任务的异步模式实现#
使用async
和await
关键字实现,在方法上使用async
关键字修饰。并且方法只能返回Task、Task<T>
、void之一。在需要等待的(创建新线程)任务使用await修饰。
注意:
- 使用async修饰的方法不一定会创建新线程。因为方法内不一定有需要异步工作的代码。
- await修饰的位置并不一定造成阻塞。
- await位置和其他代码是并行执行的。
实例:
public async static Task<long?> GetPageLength()
{
HttpClient client = new HttpClient();
var httpMessage = await client.GetAsync("http://apress.com");
return httpMessage.Content.Headers.ContentLength;
}
实例2:
public async Task<ViewResult> Index()
{
List<string> output = new List<string>();
await foreach (long? len in MyAsyncMethods.GetPageLengths(output,
"apress.com", "microsoft.com", "amazon.com"))
{
output.Add($"Page length: { len}");
}
return View(output);
}
实例:无返回值
public static async void SomeTask2()
{
await Task.Run(() => {
});
}
实例:返回Task
public static async Task SomeTask2()
{
await Task.Run(() => {
});
}
实例:返回Task
public static async Task<string> SomeTask2()
{
await Task.Run(() => {
return "Panda666";
});
return "Panda666";
}
实例:在异步方法中调用其他异步方法
async Task<int> DoSomething()
{
await DoSomethingAsync1();
await DoSomethingAsync2();
return await DoSomethingAsync3() + 1;
}
异步方法(async method)#
说明#
“异步方法”是指用async关键字修饰的方法(Asynchronous Function)。异步方法的方法名一般以Async结尾。调用异步方法一般在前面加上await关键字,表示等待该异步方法执行完成。使用await修饰后,返回的是异步方法Task<TResult>
中的TResult类型,而不再是Task<TResult>
类型。异步方法具有传染性。一个方法中如果由await调用,则这个方法必须使用async修饰。
static async Task<string> SomeMethod()
{
await Task.Delay(TimeSpan.FromSeconds(2));
return "Panda666.com";
}
异步方法支持的返回值#
async methods can have the following return types:
返回类型 | 说明 |
---|---|
Task<TResult> |
用于异步方法( async method)返回指定值。最常用的返回类型,TResult是异步方法真正返回的类型。 |
Task | 用于异步方法(async method)不返回值,但可以被等待。 |
void | 用于异步方法(async method)不返回值,但不可以被等待。 |
注意:使用Task类型必须引入命名空间using System.Threading.Tasks;
注意:Task和void区别,都用于不返回值,但返回void的方法不可以被await等待,而返回Task就可以。
语法说明#
方法使用async关键字修饰,异步方法内部使用await关键字修饰异步操作,在异步操作的地方使用await关键字修饰。异步函数内部必须至少一个await关键字,await之间不是同时运行的,而是按代码顺序运行的,即前一个await完成后才会运行后一个await。
//声明一个异步方法(async method)
async Task<int> GetLeisureHours()
{
// Task.FromResult is a placeholder for actual work that returns a string.
var today = await Task.FromResult<string>(DateTime.Now.DayOfWeek.ToString());
// The method then can process the result in some way.
int leisureHours;
if (today.First() == 'S')
leisureHours = 16;
else
leisureHours = 5;
return leisureHours;
}
异步方法限制#
- 不可以在catch、finally、lock、unsafe代码块中使用await操作符。
- 不可以对异步函数的参数使用ref、out修饰。
异步函数性能#
- 能用普通函数就不要使用异步函数,普通调用比async方法快40-~50倍,异步方法本质是转为一个带状态机的类,执行效率不高。
- 可能会占用非常多的线程。
Async/await Main#
在C# 7.1中加入了Async/await关键字对Main函数的支持。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PandaTest
{
public class Program
{
public async static Task Main(string[] args)
{
Console.WriteLine("Success");
}
}
}
异步任务转为非异步执行#
如果同样的功能,既有同步方法,又有异步方法,最好优先使用异步方法。便于调试和提高运行效率。最新的.NET Core中的操作一般都支持异步操作,但有一些方法中没用办法使用异步方法,为此需要将异步任务转为同步执行的方式来运行。
注意:虽然可以将异步方法以非异步的方式执行,但容易发生死锁问题。
异步任务转为非异步执行主要方法如下:
- 调用异步方法返回的Task实例对象的Wait()方法,适合于没用返回值的异步方法。
- 调用异步方法返回的Task实例对象的Result属性,适合于由返回值的异步方法。
实例:使用Wait()方法
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PandaTest
{
public class Program
{
public static void Main(string[] args)
{
//调用异步方法
string filePath = @"C:\Users\Administrator\Downloads\Text.txt";
ReadAllTextAsync(filePath, CancellationToken.None).Wait();
//wait
Console.WriteLine("Success");
}
/// <summary>
/// 读取文本文件(异步)
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public async static Task ReadAllTextAsync(string filePath,CancellationToken cancellationToken)
{
string result = await File.ReadAllTextAsync(filePath, cancellationToken);
Console.WriteLine(result);
}
}
}
实例:使用Result属性
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PandaTest
{
public class Program
{
public static void Main(string[] args)
{
//调用异步方法
string filePath = @"C:\Users\Administrator\Downloads\Text.txt";
//获得异步方法结果结果
string result = ReadAllTextAsync(filePath, CancellationToken.None).Result;
Console.WriteLine("文本结果");
Console.WriteLine(result);
//wait
Console.WriteLine("Success");
}
/// <summary>
/// 读取文本文件(异步)
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public async static Task<string> ReadAllTextAsync(string filePath,CancellationToken cancellationToken)
{
return await File.ReadAllTextAsync(filePath, cancellationToken);
}
}
}
异步Lambda#
说明#
类似异步方法,转为Lambda表达式即可
注意:
async Lambda表达式和具有和async方法一样的限制
async Lambda表达式必须返回void、Task、Task
实例:基本异步Lambda#
//定义
Func<int,int,Task> t1 = async (int i, int j) =>
{
await Task.Run(() => { });
};
//使用
t1(666, 888);//使用
t1(666, 888);
实例:异步委托注册到线程池#
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PandaTest
{
public class Program
{
public static void Main(string[] args)
{
//线程池中使用异步Lambda
ThreadPool.QueueUserWorkItem(async (obj) => {
string filePath = @"C:\Users\Administrator\Downloads\Text.txt";
await ReadAllTextAsync(filePath, CancellationToken.None);
});
Console.WriteLine("Success");
Console.ReadKey();
}
/// <summary>
/// 读取文本文件(异步)
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public async static Task ReadAllTextAsync(string filePath,CancellationToken cancellationToken)
{
await Task.Delay(3000);
string result = await File.ReadAllTextAsync(filePath, cancellationToken);
Console.WriteLine(result);
}
}
}
异步方法的本质#
await、async是“语法糖”,最终编译成“状态机调用”。即转为正常的方法调用,但是一个状态机,内部存在多个状态变化。
用await看似是“等待”,经过编译后,其实没有wait。async的方法会被C#编译器编译成一个类、会主要根据await调用进行切分为多个状态,对async方法的调用会被拆分为对MoveNext的调用。
await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续的代码。可以使用Thread.CurrentThread.Ma
nagedThreadId获得当前线程Id来检测前后执行的线程是否是同一个线程。就像餐馆中的服务员,如果餐馆的生意并不好,就始终是一个服务员进行服务,没有必要让多个服务员进行服务。但如果是生意好,每个流程可能都是不同的服务员。
异步与yield#
yield return不仅能够简化数据的返回,而且可以让数据处理“流水线化”,提升性能。在旧版C#中,async方法中不能用yield.从C#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可。
static async Task Main(string args)
{
await foreach(var s in Test())
{
Console.WriteLine(s);
}
}
//异步yield
static async IAsyncEnumerable<string> Test()
{
yield "returnyzk";
yield "returnyouzack";
}
异步方法注意#
-
异步方法并不是一定都在新线程中执行,如果任务执行的快有可能都在主线程上执行。
-
async方法中的异步方法如果没用使用await关键字修饰就会被当做同步方法执行。
public async static Task ReadAllTextAsync(string filePath,CancellationToken cancellationToken)
{
//被当作同步方法,不会等待
File.ReadAllTextAsync(filePath, cancellationToken);
//被当作同步方法,不会等待
Task.Delay(3000);
}
- 异步方法不一定都使用async修饰,比如可以直接返回内部异步操作的结果,而不用再次封装。只甩手Task,不“拆完了再装”(异步方法本质仍是转为普通方法调用)。这样的优点是运行效率更高,不会造成线程浪费。
//定义不用async修饰的异步方法
public static Task ReadAllTextAsync(int flag, string filePath,CancellationToken cancellationToken)
{
if(flag == 0)
{
return File.ReadAllTextAsync(filePath, cancellationToken);
}
else if(flag == 1)
{
return File.ReadAllTextAsync(filePath, cancellationToken);
}
else
{
return File.ReadAllTextAsync(filePath, cancellationToken);
}
}
//在其他异步方法中使用
await ReadAllTextAsync(1,filePath, CancellationToken.None);
- 返回Task的不一定都要标注async,标注async只是让我们更加方便的使用await,让编译器为我们代劳。
- 如果一个异步方法只是对别的异步方法调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B;把A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async。
- 如果想在异步方法中暂停一段时间,不要用Thread.Sleep(),因为它会阻塞调用线程,而要用await Task.Delay()。
- async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者来讲没区别的,因此对于接口中的方法或者抽象方法不能修饰为async。
异步等待(待合并)
说明#
.NET程序中的异步编程使用关键字:async 和 await
async关键字加在方法声明上,目的是使方法内的await关键字生效
C# 5(.NET 4.5)引入的语法糖
C# 7.1开始 Main方法也支持
C# 8.0开始,可以使用await foreach和await using
返回值:
如果async方法有返回值,应返回Task
如果没有返回值,应返回 Task
这些Task类型相当于 Future,用来在异步方法结束时通知主程序
注意
为了保持向后兼容,不要用 void 作为async方法的返回类型
async方法可以返回 void,但是这仅限于编写事件处理程序
async方法如果没有返回值,要返回Task,而不是void
规范
异步方法以Async结尾
具体原理#
异步方法需要使用async修饰,即async方法
async方法需要返回Task或者Task
在async方法内调用异步操作时,需要加上await关键字,表示异步需要等待
当async方法执行到await位置时,当前线程进入等待状态,但不会阻塞
当await位置的异步任务执行完成后,会回到当前线程中继续执行后面的代码
桌面软件与async/await#
一般情况下任何UI组件的交互都是在同一个UI线程中发生的
如果一个UI事件操作事件的执行需要大量时间会造成UI线程卡顿
用户会明显感到软件界面卡死
解决办法就是使用异步方式执行事件处理函数
//使用async修饰方法
private async void SomeControl_Click(object sender, EventArgs e)
{
//内部对异步操作使用await修饰
await xxxx.xxxAsync();
}
作者:重庆熊猫
出处:https://www.cnblogs.com/cqpanda/p/16749422.html
版权:本作品采用「不论是否商业使用都不允许转载,否则按3元1字进行收取费用」许可协议进行许可。
本文来自博客园,作者:重庆熊猫,转载请注明原文链接:https://www.cnblogs.com/cqpanda/p/16749422.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!