async await 异步方法 错误的理解与运行原理解说

6 篇文章 0 订阅
5 篇文章 0 订阅

1、错误的理解

  1. 以为 async 方法与 Task 是相同原理,只要被调用就会自动开辟新的异步线程?
  2. 以为一个 await 在等待期间会继续执行下一个 await,直到第一次引用 await 结果集的时候才会开始等待它的返回结果?

经过后续不断的测试,发现完全不是自己开头想象的那样,请参考以下的总结。


2、运行原理解说

  • async 异步方法起初设计主要是用来解决WinForm单线程运行时出现的UI阻塞问题;
  • async 可理解为是一个可与主线程轮流共生的方法;

1、当执行到await任务时、主线程的指针会自动切换到上一处 没有使用await调用async方法 的位置、继续往后运行;
           (这时就释放了UI线程,从而解决了WinForm单线程UI阻塞的问题)

2、当await任务运行完毕后、主线程的指针会再自动切回来;                                                                                         
           (这时的async方法就又回到平时的单线程状态了、同时UI也被重新锁定了,所以可以直接修改、无需使用Control.Invoke() ) 

看了上边的解说后应该就清楚以下的几个疑惑了吧?

问:为什么 async 方法内部必须得有 await ?
答:因为await任务才是异步的主要;

问:为什么 async 方法没有 await 就不会异步运行?
答:因为await任务才是异步的主要;

问:调用 async 方法后会立即开辟异步线程?
答:不是,先是同步运行,直到运行到内部的await时才会临时变成异步,当await运行完毕后会再切回去;

问:调用 async 方法必须使用 await 开头?
答:不是,如果不用即时结果的情况可以不用await,可使用Task<T>异步接收;

问:为什么 await 只能写在 async 方法中?
答:因为系统需要对异步方法做特殊处理才能实现异步,所以需要在异步方法的开头加上async关键字做标记;

问:为什么 async 中写了多个 await 却还是同步运行?
答:因为此异步非多线程异步、而是单线程异步,每次运行到await时都会临时释放当前线程,让先去处理当前方法以外的事情;

问:await 等待和 Task.Result 等待有什么不同?
答:Task.Result 是占用当前线程资源的同步等待,不像await会临时释放当前线程;

问:async 异步方法适用于哪些场景?
答:比如需要实现在单线程下处理多个相互不冲突的任务时,避免单个业务出现多线程难以管理;


3、使用方法

  1. 程序需要先升级到.NET4.5框架、或.NET4.0框架通过NuGet安装 Microsoft.Bcl.Async ;
  2. async 方法无法使用out和ref参数,且返回值必须是void或Task或Task<TResult>;
  3. async 方法后的后缀名应以"Async"做为结尾,如“DownloadAsync”;
  4. await 只能在async方法内使用;
  5. await 需要和Task搭配使用;
  6. await 在多次出现时会按顺序运行;
  7. await 任务发生故障时会直接抛给主线程;

4.1、模拟单线程处理多任务

效果:

代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Program
    {
        private static Stopwatch watch = new Stopwatch();

        public static void Main()
        {
            watch.Start();

            //模拟单线程运行多任务,同时还可以避免抢夺资源导致出现死锁的情况
            var tasks = new List<Task<string>>();
            {
                tasks.Add(GetDataAsync("事务A")); //当内部执行到await时会临时跳出,此时的程序会在此处继续往后走,直到await完成时再切回去;
                tasks.Add(GetDataAsync("事务B")); //当内部执行到await时会临时跳出,此时的程序会在此处继续往后走,直到await完成时再切回去;
                tasks.Add(GetDataAsync("事务C")); //当内部执行到await时会临时跳出,此时的程序会在此处继续往后走,直到await完成时再切回去;
            }

            OutPut("开始等待...(此处可以处理一些其他事务,利用上等待期间的空闲资源)");

            Task.WaitAll(tasks.ToArray());

            OutPut("全部完成!" + string.Join("", tasks.Select(x => "\r\n\t" + x.Result).ToArray()));

            watch.Stop();
            Console.ReadKey();
        }
        /// <summary>
        /// 模拟异步获取数据
        /// </summary>
        /// <param name="name">参数</param>
        /// <returns></returns>
        public static async Task<string> GetDataAsync(string name)
        {
            OutPut($"开始 {name}");

            string result = "";

            //await 可以临时释放线程,待完成后再折回
            result += " " + await Task.Run(delegate
            {
                Thread.Sleep(3000); //模拟延时操作
                return "数据A";
            });

            //await 可以临时释放线程,待完成后再折回
            result += " " + await Task.Run(delegate
            {
                Thread.Sleep(3000); //模拟延时操作
                return "数据B";
            });
            result = $"{name}:{result}";

            OutPut($"完成 {result}");

            return result;
        }
        /// <summary>
        /// 输出
        /// </summary>
        /// <param name="msg"></param>
        public static void OutPut(string msg) => Console.WriteLine($"{(watch.ElapsedMilliseconds / 1000D).ToString("0.000s")} <{Thread.CurrentThread.ManagedThreadId.ToString("00")}> {msg}");
    }
}

4.2、模拟 WinForm 异步运行,防止UI阻塞

效果:

代码:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        /// <summary>
        /// 模拟异步加载、异步更新UI(原方法)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            var start = DateTime.Now;

            textBox1.Text = Log($"开始");
            
            //创建一个异步任务,为释放主线程,防止UI线程阻塞
            Task.Run(delegate
            {
                //模拟一个耗时1秒的同步任务
                Thread.Sleep(1000);
                //异步更新UI
                textBox1.BeginInvoke((Action)delegate
                {
                    textBox1.Text += Log($"完成1 <{(DateTime.Now - start).TotalSeconds.ToString("0.00s")}>");
                });

                //模拟一个耗时1秒的同步任务
                Thread.Sleep(1000);
                //异步更新UI
                textBox1.BeginInvoke((Action)delegate
                {
                    textBox1.Text += Log($"完成2 <{(DateTime.Now - start).TotalSeconds.ToString("0.00s")}>");
                });

                //模拟一个耗时1秒的同步任务
                Thread.Sleep(1000);
                //异步更新UI
                textBox1.BeginInvoke((Action)delegate
                {
                    textBox1.Text += Log($"完成3 <{(DateTime.Now - start).TotalSeconds.ToString("0.00s")}>");
                });
            });
        }
        /// <summary>
        /// 模拟异步加载、异步更新UI(新方法)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void button2_Click(object sender, EventArgs e)
        {
            var start = DateTime.Now;

            textBox2.Text = Log($"开始");
            
            //模拟一个同步任务,使用异步运行 (此处通常是一个Task<T>)
            await Task.Delay(1000);
            //更新UI
            textBox2.Text += Log($"完成1 <{(DateTime.Now - start).TotalSeconds.ToString("0.00s")}>");

            //模拟一个同步任务,使用异步运行 (此处通常是一个Task<T>)
            await Task.Delay(1000);
            //更新UI
            textBox2.Text += Log($"完成2 <{(DateTime.Now - start).TotalSeconds.ToString("0.00s")}>");

            //模拟一个同步任务,使用异步运行 (此处通常是一个Task<T>)
            await Task.Delay(1000);
            //更新UI
            textBox2.Text += Log($"完成3 <{(DateTime.Now - start).TotalSeconds.ToString("0.00s")}>");

            //模拟一个耗时的任务,使用异步运行,完成后自动更新UI
            textBox2.Text += Log($@"{await Task.Run(delegate
            {
                Thread.Sleep(1000);
                return $"完成4 <{(DateTime.Now - start).TotalSeconds.ToString("0.00s")}>";
            })}");
        }
        /// <summary>
        /// 日志生成
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private string Log(string text) => $"\r\n{DateTime.Now.ToString("HH:mm:ss:fff")} <Thread-{Thread.CurrentThread.ManagedThreadId.ToString("00")}> {text}";
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值