c#Async,await编程核心基础,执行顺序,死锁,使用注意点,

第一个知识点:调用async异步方法的线程,遇到await,马上回去继续执行后面的操作,

而async异步方法里面,则是执行完任务,再继续执行await关键字后面的内容,

相当于await关键字后面内容,是在回调中完成,可在第三篇代码中体会这一点,对比task.ContinueWith()

呈现出一定程度的有序性,这就是大家讨论的同步编程方式写异步

本质是语法糖,编译器进行编译,基于状态机模式实现

 这里await必须有async,不能单独存在,并且他是修饰Task
 加了async和await支行,原本返回void,可以返回Task,原本返回T类型,返回Task
 如下,一般不返回void如上,使用时就不能加await

第二个知识点:通过await访问任务,而不是Result

  Result 属性为阻止属性。 如果你在其任务完成之前尝试访问它,当前处于活动状态的线程将被阻止,直到任务完成且值为可用。 在大多数情况下,应通过使用 await 访问此值,而不是直接访问属性。
 上一示例通过检索 Result 属性的值来阻止主线程,从而使 ShowTodaysInfo 方法可在应用程序结束之前完成执行。
             https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/async-return-types#task-return-type

一句话,异步方法用Result非常容易陷入死锁,

Result先阻塞主线程,等待异步方法执行完,但异步方法体执行完,却return回不去主线程(它被阻塞)

Result先阻塞主线程,等待异步方法执行完,但异步方法体执行完,却return回不去主线程(它被阻塞)

Result先阻塞主线程,等待异步方法执行完,但异步方法体执行完,却return回不去主线程(它被阻塞)

  await关键字,
           只能在通过 async 关键字修改的方法、lambda 表达式或匿名方法中使用 await 运算符。 在异步方法中,不能在同步函数的主体、lock 语句块内以及不安全的上下文中使用 await 运算符。
await 运算符的操作数通常是以下其中一个 .NET 类型:Task、Task、ValueTask 或 ValueTask。 但是,任何可等待表达式都可以是 await 运算符的操作数。 有关详细信息,请参阅 C# 语言规范中的可等待表达式部分。
如果表达式 t 的类型为 Task 或 ValueTask,则表达式 await t 的类型为 TResult。 如果 t 的类型为 Task 或 ValueTask,则 await t 的类型为 void。 在这两种情况下,如果 t 引发异常,则 await t 将重新引发异常。 有关如何处理异常的详细信息,请参阅 try-catch 语句一文中的异步方法中的异常部分。
async 和 await 关键字在 C# 5 和更高版本中都可用。
 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/await
官方说的很清楚,await只能用在以上类型的操作数, 

备注:为了看得子线程输出内容,一定要在Main函数中,添加Console.Read();

//异步多线程调用方式,本质是一种语法糖,并不是什么新的调用方式
            new AsyncClass().Show();
            //这个Read非常关键,相当于一直在等待其他子线程去完成
            Console.Read();
            return;         

并且窗体程序选中控制台输出, 

体会执行顺序 

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

namespace AsyncDemo
{
    class AsyncDemo
    {
        //一般多线程开启
        public void Show() {
            Console.WriteLine("This is Main Start " + Thread.CurrentThread.ManagedThreadId);//1
            //第一:
            //一般多线程方法:挂起开启子线程内容,其他内容正常执行,即1,2,3,4,5,6
            //this.NoReturn();

            //第二: //加上异步之后,子线程第一步和主线程第二步,并发执行,最大变化是第三步
            this.NoReturnAsync();
           
            //第三:加上回调
            //this.NoReturnWithContinue();
            //this.ReturnTaskAsync();
            Console.WriteLine("This is Main End " + Thread.CurrentThread.ManagedThreadId);//4 主线程第二步
        }
        public void NoReturn()
        {
            Console.WriteLine("This is NoReturn Method Start " + Thread.CurrentThread.ManagedThreadId);//2
            var task = Task.Run(() =>
            {
                Console.WriteLine("This is NoReturn task Start " + Thread.CurrentThread.ManagedThreadId);//5 子线程第一步
                Thread.Sleep(2000);
                Console.WriteLine("This is NoReturn task End " + Thread.CurrentThread.ManagedThreadId);//6
            });
            //task.Wait();
            Console.WriteLine("This is NoReturn Method End " + Thread.CurrentThread.ManagedThreadId);//3
        }

        public void NoReturnWithContinue()
        {
            Console.WriteLine("This is NoReturn Method Start " + Thread.CurrentThread.ManagedThreadId);//2
            var task = Task.Run(() =>
            {
                Console.WriteLine("This is NoReturn task Start " + Thread.CurrentThread.ManagedThreadId);//5 子线程第一步
                Thread.Sleep(2000);
                Console.WriteLine("This is NoReturn task End " + Thread.CurrentThread.ManagedThreadId);//6
            });
            //task.Wait();
            task.ContinueWith(t => {
                Console.WriteLine("This is NoReturn Method End " + Thread.CurrentThread.ManagedThreadId);//3
            });
            
        }

        //async可以单独写,此时和没写差不多
        public async void NoReturnAsync()
        {
            Console.WriteLine("This is NoReturn Method Start "+Thread.CurrentThread.ManagedThreadId);//2
            var task = Task.Run(()=>{
                Console.WriteLine("This is NoReturn task Start " + Thread.CurrentThread.ManagedThreadId);//3
                Thread.Sleep(2000);
                Console.WriteLine("This is NoReturn task End " + Thread.CurrentThread.ManagedThreadId);
            });
            await task;
            Console.WriteLine("This is NoReturn Method End " + Thread.CurrentThread.ManagedThreadId);

            var task2 = Task.Run(() =>
            {
                Console.WriteLine("This is NoReturn2 task Start " + Thread.CurrentThread.ManagedThreadId);//3
                Thread.Sleep(2000);
                Console.WriteLine("This is NoReturn2 task End " + Thread.CurrentThread.ManagedThreadId);
            });
            await task2;
            Console.WriteLine("This is NoReturn2 Method End " + Thread.CurrentThread.ManagedThreadId);
        }
        //这里await必须有async,不能单独存在,并且他是修饰Task
        //加了async和await支行,原本返回void,可以返回Task,原本返回T类型,返回Task
        //如下,一般不返回void如上,使用时就不能加await
        //
        public async Task ReturnTaskAsync()
        {
            Console.WriteLine("This is ReturnTaskAsync Method Start " + Thread.CurrentThread.ManagedThreadId);
            var task = Task.Run(() =>
            {
                Console.WriteLine("This is ReturnTaskAsync task Start " + Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(2000);
                Console.WriteLine("This is ReturnTaskAsync task End " + Thread.CurrentThread.ManagedThreadId);
            });
            await task;
            //最大的区别在这里,遇到await,异步方法调用结束,回到主线程执行,
            //它后面的内容,以及子线程中内容,还在执行,事实应该是子线程中饭内容执行完,再执行await后面内容,
            //同步方式编写异步
            Console.WriteLine("This is ReturnTaskAsync Method End " + Thread.CurrentThread.ManagedThreadId);
        }
    }
}

 阻塞的例子:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //官方说的很清楚,await只能用在以上类型的操作数,
        private async void button1_Click(object sender, EventArgs e)
        {
            //string result = MethodAsync().Result;
            string result = await MethodAsync();
        }


      
        //分析死锁原因,它必须等待MethodAsync执行完毕,等待return,但是这个异步方法的return,是在主线程中执行,
        //首先主线程阻塞,因为它要等待异步方法执行完,但是异步方法本身return,需要在主线程中完成,但这时主线程阻塞起来,也就是子线程永远等不到执行完  
        private void button2_Click(object sender, EventArgs e)
        {
            //这里死锁形成
            textBox1.Text = MethodAsync().Result;
            //可以再包裹一个子线程,在子线程结束完成进行一些处理,然而明显更加麻烦,
            //还不如将封装的方法,直接放在这个线程中
            //var t = Task.Run(() =>
            //{
            //    return MethodAsync().Result;
            //});
            //t.ContinueWith((task =>
            //{
            //    textBox1.Text = task.Result;
            //}));
        }
        
        
        private async Task MethodAsync()
        {
            Task ResultFromTimeConsumingMethod = TimeConsumingMethod();           
            return await ResultFromTimeConsumingMethod;
        }

        //这个函数就是一个耗时函数,可能是IO操作,也可能是cpu密集型工作。
        private Task TimeConsumingMethod()
        {
            var task = Task.Run(() =>
            {
                Thread.Sleep(5000);
                return "Hello I am TimeConsumingMethod 1";
            });
            return task;
        }

       

       
        //自身就是一个异步方法
        private async void button3_Click(object sender, EventArgs e)
        {
            var t = Task.Run(() =>
            {
                Thread.Sleep(5000);
                return "Hello I am TimeConsumingMethod 3";
            });
            textBox1.Text = await t;
        }

        private void button4_Click(object sender, EventArgs e)
        {
            var t = Task.Run(() =>
            {
                Thread.Sleep(5000);
                //放在这里失去,执行某个耗时过程得到结果,再赋值的意义
                //textBox1.Text = "测试";
                return "Hello I am TimeConsumingMethod 4";
            });

            //一个不错处理方式,将异步完成里面,添加赋值即,continueWith方法
            t.ContinueWith((task => {
                textBox1.Text = task.Result;
            }));

            //这里用Result暂时阻塞
            //textBox1.Text = t.Result;
        }
    }
}

 

你可能感兴趣的:(c#)