• C# async / await 用法


    目录

    一、简介

    二、异步等待返回结果

    三、异步方法返回类型

    四、await foreach

    五、Task.Delay

    结束


    一、简介

    await 运算符暂停对其所属的 async 方法的求值,直到其操作数表示的异步操作完成。 异步操作完成后,await 运算符将返回操作的结果(如果有)。 当 await 运算符应用到表示已完成操作的操作数时,它将立即返回操作的结果,而不会暂停其所属的方法。 await 运算符不会阻止计算异步方法的线程。 当 await 运算符暂停其所属的异步方法时,控件将返回到方法的调用方。

    二、异步等待返回结果

    下面就演示 await 运算符常用的一些用法。

    新建一个基于 .Net6 的 Winform 项目,界面就两个按钮,如下:

    代码 

    1. namespace 异步编程
    2. {
    3. public partial class Form1 : Form
    4. {
    5. public Form1()
    6. {
    7. InitializeComponent();
    8. }
    9. private void button1_Click(object sender, EventArgs e)
    10. {
    11. IsTrue = false;
    12. AwaitEnd();
    13. }
    14. private void button2_Click(object sender, EventArgs e)
    15. {
    16. IsTrue = true;
    17. }
    18. private bool IsTrue = false;
    19. private Task<string> StartTimer()
    20. {
    21. var t = Task.Run(() =>
    22. {
    23. while (true)
    24. {
    25. if (IsTrue)
    26. return "555";
    27. Thread.Sleep(100);
    28. }
    29. });
    30. return t;
    31. }
    32. private async void AwaitEnd()
    33. {
    34. Console.WriteLine("开始执行,时间:" + DateTime.Now.ToString());
    35. var res = await StartTimer();
    36. Console.WriteLine("结束:" + res + " 时间:" + DateTime.Now.ToString());
    37. }
    38. }
    39. }

    点击按钮1开始启动异步,点击按钮2,就返回结果,如果不点击按钮2,那么 while 循环就不会停止。

    效果:

    可以看到,点击了按钮1后,并不会让主线程卡死,窗体还是可以随意的拖动的,直到 StartTimer 方法将返回值返回回来后,才会继续执行后续的代码,这对一些需要阻塞线程,并获取另外的计算结果,然后才能继续计算的需求而言,有极大的帮助,比如读取数据库数据,如果网速比较慢,并且不会立刻就返回结果,用 await 运算符就可以在同一个方法里,等到获取到数据库返回结果后,再进行下一步运算,而不是从上到下,一下子就执行完了。

    下面是以前我查询数据库写的代码,效果和上面演示中的 await 运算符是一样的,在下面的方法中,使用 Action 回调,代码都没写到一起,虽然逻辑一样,但用起来就不是那么的方便。

    1. ///
    2. /// 执行SQL语句,并获取值
    3. ///
    4. ///
    5. ///
    6. private static void ExecuteAndReturnValue(string sql, Action callBack)
    7. {
    8. Func Funcs = () =>
    9. {
    10. DataSet dataSet = MySqlHelper.GetDataSet(sql);
    11. if (dataSet == null || dataSet.Tables.Count == 0)
    12. return null;
    13. return dataSet.Tables[0];
    14. };
    15. //执行任务
    16. Task printRes = Task.Run(Funcs);
    17. //等待任务完成
    18. printRes.GetAwaiter().OnCompleted(() =>
    19. {
    20. if (callBack != null)
    21. callBack(printRes.Result);
    22. });
    23. }

    当前的示例,只是执行单个任务,如果有多个任务,用下面的方法也是可以的,

    1. private Task DoSomethingAsync(int x)
    2. {
    3. return Task.Run(() =>
    4. {
    5. Thread.Sleep(1000);
    6. Console.WriteLine("值:" + x);
    7. });
    8. }
    9. public async Task RunAsync()
    10. {
    11. foreach (var x in new[] { 1, 2, 3 })
    12. {
    13. await DoSomethingAsync(x);
    14. }
    15. }

    DoSomethingAsync 方法中,返回值可以从另一个数组中获取到 Task 并执行,我这里就不写那么仔细了,如果用面向过程的写法,就是这么写的:

    1. private async void Test()
    2. {
    3. await Task.Run(async () =>
    4. {
    5. await Task.Delay(4000);
    6. Trace.WriteLine("第1个线程执行");
    7. });
    8. await Task.Run(async () =>
    9. {
    10. await Task.Delay(3000);
    11. Trace.WriteLine("第2个线程执行");
    12. });
    13. await Task.Run(async () =>
    14. {
    15. await Task.Delay(2000);
    16. Trace.WriteLine("第3个线程执行");
    17. });
    18. }

    三、异步方法返回类型

    在方法里加上了 async 关键字后,返回值就只能使用固定的几个了,不然会报错。

    异步函数的返回类型只能为: void、Task、TaskValueTask 或 ValueTask

    Task: 代表一个返回值T类型的操作。

    Task: 代表一个无返回值的操作。

    void: 为了和传统的事件处理程序兼容而设计。

    四、await foreach

    可以使用 await foreach 语句来使用异步数据流,即实现 IAsyncEnumerable 接口的集合类型。 异步检索下一个元素时,可能会挂起循环的每次迭代。

    代码

    1. private async void Test()
    2. {
    3. IAsyncEnumerable<int> pullBasedAsyncSequence = ProduceAsyncSumSeqeunc(5);
    4. //开始另一项任务;用于使用异步数据序列!
    5. var consumingTask = Task.Run(() => ConsumeAsyncSumSeqeunc(pullBasedAsyncSequence));
    6. await Task.Delay(TimeSpan.FromSeconds(3));
    7. Console.WriteLine("搞一些其他事");
    8. //只是为了演示!等待任务完成!
    9. await consumingTask;
    10. Console.WriteLine("异步流演示完成!" );
    11. }
    12. private async Task ConsumeAsyncSumSeqeunc(IAsyncEnumerable<int> sequence)
    13. {
    14. Console.WriteLine("执行 ConsumeAsyncSumSeqeunc 方法");
    15. await foreach (var value in sequence)
    16. {
    17. Console.WriteLine($"value: {value}");
    18. await Task.Delay(TimeSpan.FromSeconds(1));
    19. };
    20. }
    21. private async IAsyncEnumerable<int> ProduceAsyncSumSeqeunc(int count)
    22. {
    23. Console.WriteLine("执行 ProduceAsyncSumSeqeunc 方法");
    24. int index = 0;
    25. for (int i = 0; i < count; i++)
    26. {
    27. await Task.Delay(TimeSpan.FromSeconds(0.5));
    28. yield return index += count;
    29. }
    30. }

    调用 Test 方法后,即可打印

    五、Task.Delay

    解释:创建将在时间延迟后完成的任务。命名空间: System.Threading.Tasks

    在上面的演示中用到了多次,例:

    await Task.Delay(TimeSpan.FromSeconds(0.5));

    在 Delay 方法中,可以用 TimeSpan 中的时、分、秒 表示

    参考:

    Delay(Int32)

    创建一个在指定的毫秒数后完成的任务。

    Delay(TimeSpan)

    创建一个在指定的时间间隔后完成的任务。

    Delay(Int32, CancellationToken)

    创建一个在指定的毫秒数后完成的可取消任务。

    Delay(TimeSpan, CancellationToken)

    创建一个在指定的时间间隔后完成的可取消任务。

    结束

    如果这个帖子对你有所帮助,欢迎 关注 、点赞 、留言

    end

  • 相关阅读:
    JavaWeb之会话技术&会话固定攻击
    【最新案例】网络智能类SCI&EI,极速审稿,录用到检索仅2个月零9天
    Anaconda和PyCharm比较
    渗透测试-域内密码凭证获取
    ES集群安装遇到的一些错误
    记录--用JS轻松实现一个录音、录像、录屏的工具库
    nacos注册发现原理
    SH-CST 2022丨SpeechHome 语音技术研讨会
    搜索算法总结
    Epoll:让IO多路复用变得有趣
  • 原文地址:https://blog.csdn.net/qq_38693757/article/details/127867464