1. 程式人生 > >C# async與await的使用說明

C# async與await的使用說明

C# 非同步程式設計提供了兩個關鍵字,async 和await,這裡說明下怎麼用
C# 5 引入了一種簡便方法,即非同步程式設計。此方法利用了 .NET Framework 4.5 及更高版本、.NET Core 和 Windows 執行時中的非同步支援。 編譯器可執行開發人員曾進行的高難度工作,且應用程式保留了一個類似於同步程式碼的邏輯結構。 因此,你只需做一小部分工作就可以獲得非同步程式設計的所有好處。

本主題概述了何時以及如何使用非同步程式設計,幷包括指向包含詳細資訊和示例的支援主題的連結。

用法
async 用在方法定義前面,await只能寫在帶有async標記的方法中。
注意await非同步等待的地方,await後面的程式碼和前面的程式碼執行的執行緒可能不一樣


async關鍵字建立了一個狀態機,類似yield return 語句;await會解除當前執行緒的阻塞,完成其他任務

0X01 簡單用法

測試程式碼

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

namespace AsyncAndAwait_Test
{
    class Program
{ static void Main(string[] args) { Console.WriteLine("主執行緒ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId); AsyncTest(); } static async void AsyncTest() { Console.WriteLine("*******Start************ManagedThreadId:"
+ Thread.CurrentThread.ManagedThreadId); Task<int> taskA= Print(); Console.WriteLine("*********Middle**********ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId); int a = await taskA; Console.WriteLine("*********End**********ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId); } static Task<int> Print() { var tcs = new TaskCompletionSource<int>(); var thrd = new Thread(() => { Console.WriteLine("子執行緒ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId); for (int i = 0; i < 3; i++) { Console.WriteLine("===========等待==========" + i); System.Threading.Thread.Sleep(1000); } tcs.SetResult(99); }); thrd.Start(); return tcs.Task; } } }

結果
在這裡插入圖片描述

這裡的結果紅框這個地方執行緒id變了,按照我的理解是非同步呼叫await前面程式碼的執行緒和await後面程式碼的執行緒可能不一樣

再看一個測試例子

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

namespace AsyncAndAwait_Test
{
    class Program
    {
        /// <summary>
        /// async 和await 實踐測試
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            
            var tcs1 = new TaskCompletionSource<int>();
            var tcs2 = new TaskCompletionSource<int>();
            Console.WriteLine("主執行緒ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
            AsyncTest(tcs1,tcs2);

            Thread.Sleep(1000);
            tcs1.SetResult(100);

            Thread.Sleep(5000);
            tcs2.SetResult(100);
            Console.WriteLine("主執行緒結束ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
        }
        static async void AsyncTest(TaskCompletionSource<int> tcs1, TaskCompletionSource<int> tcs2)
        {
            Console.WriteLine("AsyncTest方法執行的執行緒id:" + Thread.CurrentThread.ManagedThreadId);
            //await後執行緒出現變化,非同步執行(執行緒id不一樣)
            int a = await Print();
            Console.WriteLine("11****await tcs3後的執行緒id:" + Thread.CurrentThread.ManagedThreadId);
            //await後的執行緒沒有變化,是因為SetResult比較早,這裡可以直接同步執行(我認為的)
            int b = await tcs1.Task;
            Console.WriteLine("22****await tcs1後的執行緒id:" + Thread.CurrentThread.ManagedThreadId);
            //await後執行緒出現變化,非同步執行(執行緒id不一樣)
            int c= await tcs2.Task;
            Console.WriteLine("33****await tcs2後的執行緒id:" + Thread.CurrentThread.ManagedThreadId);
        }


        static Task<int> Print()
        {
            var tcs3 = new TaskCompletionSource<int>();
            var thrd = new Thread(() =>
            {
                Console.WriteLine("子執行緒ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
                for (int i = 0; i < 3; i++)
                {
                    Console.WriteLine("===========等待==========" + i);
                    System.Threading.Thread.Sleep(1000);
                }
                tcs3.SetResult(99);

            });
            thrd.Start();
            return tcs3.Task;
        }
    }
}

結果:
在這裡插入圖片描述
程式碼裡面await用法,這裡非同步返回的是一個Task<int>型別,通過await等待Task執行完成後返回的是int,或者返回的是T模板

根據結果總結結論如下:
如果執行到await時TaskCompletionSource沒有設定SetResult、SetCanceled、SetException中的一個則await後面的執行緒可能出現變化(非同步操作)
如果執行到await時TaskCompletionSource有設定SetResult、SetCanceled、SetException中的一個則await後面的執行緒不變化(同步操作)

0X02 深入用法

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

namespace AsyncAwaitTest
{
    class Program
    {
        static void Main(string[] args)
        {

            Main1();
            Main2();
        }


        static void Main1()
        {
            AsyncMethod1();
            Console.WriteLine("Main finished!!!");
            //這裡是為了處理Task啟動一個後臺執行緒的問題,主執行緒結束時,後臺執行緒自動關閉
            Thread.Sleep(5000);
        }
        static async void AsyncMethod1()
        {
            Console.WriteLine("主執行緒id:" + Thread.CurrentThread.ManagedThreadId);


            //切換執行緒等待完成
            string res = await Task.Run<string>( () =>{
                Thread.Sleep(1000);
                Console.WriteLine("func thread id:" + Thread.CurrentThread.ManagedThreadId);
                return "hello";
            });

            Console.WriteLine("AsyncMethod finished!!!");

        }

        /// <summary>
        /// 
        /// </summary>
        static void Main2()
        {
            Console.WriteLine("===============================================");
            AsyncMethod2();
            Console.WriteLine("Main2 finished!!!");
            //這裡是為了處理Task啟動一個後臺執行緒的問題,主執行緒結束時,後臺執行緒自動關閉
            Thread.Sleep(5000);
        }
        /// <summary>
        /// 兩個task同時執行
        /// </summary>
        static async void AsyncMethod2()
        {
            Task<string> t1 = GreetingAsync("Wang",500);
            Task<string> t2 = GreetingAsync("Codingriver");
            //兩個非同步task同時執行,直到所有task執行完 await才返回
            await Task.WhenAll(t1, t2);
            Console.WriteLine("result:::" + t1.Result + "    " + t2.Result);
        }




        static Task<string>GreetingAsync(string name,int time=1000)
        {
            return Task.Run<string>(()=>{
                Thread.Sleep(time);
                return Greeting(name);
            });
        }

        static string Greeting(string name)
        {
            return "Hello " + name;
        }
    }
}

結果:
在這裡插入圖片描述

非同步的異常捕獲需要注意的是要將非同步await放在try catch塊中,且返回Task泛型,大概是這個意思
捕獲多個非同步的異常例子:

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

namespace AsyncAwaitTest
{
    class Program
    {
        static void Main(string[] args)
        {
            TestMoreTaskException();
        }




        static async void TestMoreTaskException()
        {

            Task results=null;
            try
            {

                Task<string> t1 = GreetingAsync(null, 500);
                Task<string> t2 = GreetingAsync("Codingriver");
                //兩個非同步task同時執行,直到所有task執行完 await才返回
                await(results= Task.WhenAll(t1, t2));
                Console.WriteLine("TestMoreTaskException");


            }
            catch (Exception ex)
            {
                //AggregateException
                Console.WriteLine("Exception:::::::::" + ex.ToString());
                foreach (var item in results.Exception.InnerExceptions)
                {
                    Console.WriteLine("InnerException:::::::::" + item.ToString());
                }
            }

        }
        static Task<string> GreetingAsync(string name, int time = 1000)
        {
            return Task.Run<string>(() =>
            {
                Thread.CurrentThread.IsBackground = false;
                Thread.Sleep(time);
                throw new Exception("codingriver test");
                return string.Empty;
            });
        }
    }
}

結果:
在這裡插入圖片描述

參考文章
使用 Async 和 Await 的非同步程式設計 (C#)