你眼中的async/await是什么樣的?


又到了周末的code review環節,這次code review發現了一個對async/await的理解問題。讓我們直奔主題:

            var foodsSearch = new FoodSearchService().SearchAsync();
            var fruitsSearch = new FruitSearchService().SearchAsync();

            var foods = await foodsSearch;

            foods.ForEach(f => Console.WriteLine("food:{0}", f.Name));

            Console.WriteLine("done");

這是一段使用async/await的代碼,算得上是async/await的最佳使用實踐。問題出在大家對這段代碼理解各有不同,讓我們來看看如何理解這段代碼:

這個理解正確嗎?團隊的爭論焦點在步驟2上,大家錯誤的認為await關鍵字就是等待的意思,為了在第三步拿到結果,線程在步驟2處等待直到FoodSearchService返回結果。如果你也是這樣理解的,那么你應該看看下面的代碼如何理解?

            var foodsSearch = new FoodSearchService().SearchAsync();
            var fruitsSearch = new FruitSearchService().SearchAsync();

            var foods = foodsSearch.Result;

            foods.ForEach(f => Console.WriteLine("food:{0}", f.Name));

            Console.WriteLine("done");

剛才錯誤的理解正好是這段代碼的描述。那么第一段使用async/await的代碼如何理解?

使用await標記的方法調用(上面例子中的foodsSearch)不會阻塞主線程,主線程在步驟2不會等待。

為了說明這個結論,我們用下面的代碼模擬await(僅僅是模擬其行為,並不能真確執行),我沒有研究過await的實現,但是下面的代碼跟await具有相同的行為:

            var foodsSearch = new FoodSearchService().SearchAsync();
            var fruitsSearch = new FruitSearchService().SearchAsync();

            var callback = foodsSearch as ICallBackRegister;
            callback.Register<List<Food>>(foods =>
            {
                foods.ForEach(f => Console.WriteLine("food:{0}", f.Name));
                Console.WriteLine("done");
            }).Await(foodsSearch);

為了方便大家理解,我們寫一個簡單的ICallBackRegister實現:

    public class CallbackRegister:ICallBackRegister
    {
        Action<object> _action; 
        public ICallBackRegister Register<T>(Action<T> callback)
        {
            _action = o => callback((T) o);
            return this;
        }

        public void Await(Task task)
        {
            task.ContinueWith((result)=>_action(result));
        }
    }

從上面模擬的代碼中我們可以得出兩個結論:

1、沒有任何阻塞主線程的代碼。

2、盡量推遲await的調用,能在步驟2使用await就不要在步驟1使用。因為一旦使用了await,后面所有的代碼都變成了await所調用對象的回調,無法跟之前的異步代碼並行。即便你在步驟1就使用了await,只能說FoodSearchService和FruitSearchService兩者不能並行,但是任然不會阻塞主線程——在主線程上永遠沒有等待這一說。

我們再看張圖來解釋一下這期間發生的事情:

設想這樣的代碼放在一個GUI中Button的click事件中,由於await不會阻塞主線程,界面再不會有假死的情況發生。

            this.BackColor = Color.Aquamarine;
            this.btnAsyncAwait.BackColor = Color.Blue;

            var operationA = new LongTimeOperationA().GetValueAsync();
            var operationB = new LongTimeOperationB().GetValueAsync();

            var valueA =await operationA;
            Text = valueA;

同樣的道理,在web mvc編程中,如果controller和EF中全程使用async/await,此時假設用戶有一個請求過來,IIS會從線程池中取出一個線程來響應用戶請求,由於主線程沒有任何阻塞,所以IIS會很快將線程回收到了線程池中。當EF返回數據並且返回ActionResult時,IIS再次從線程池中拿出一個線程來對用戶請求做響應。所以

正是由於async/await不會阻塞主線程,我們才說async/await會提高IIS的響應能力。

另外async/await的使用並不會提升訪問數據庫的效率,該花多長時間還得花多長時間。

最后我們給出Task.Result版本的click事件,由於調用Task.Result會阻塞主線程,所以你可以看到界面假死的現象。

            this.BackColor = Color.Beige;
            this.btnTaskResult.BackColor = Color.BlueViolet;

            var operationA=new LongTimeOperationA().GetValueAsync();
            var operationB=new LongTimeOperationB().GetValueAsync();

            var valueA = operationA.Result;
            Text = valueA;

代碼下載:download


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM