1. 程式人生 > >關於Task的一點思考和建議

關於Task的一點思考和建議

建議非同步返回Task或Task<T>

當在.NET Core中寫爬蟲用到非同步去下載資源後接下來進行處理,對於處理完成結果我返回void,想到這裡不僅僅一愣,這麼到底行不行,翻一翻寫的第一篇部落格,只是提醒了我下不要用void,至於為何不用也沒去探討,接下來我們來探討下返回值為Task和void,至於Task<T>這個和Task類似。我們直接看程式碼,首先演示void,如下:

複製程式碼
        private static async void ThrowExceptionAsync()
        {
            await Task.Delay(TimeSpan.FromSeconds(1
)); throw new InvalidOperationException(); } private static void AsyncVoidExceptions_CannotBeCaughtByCatch() { try { ThrowExceptionAsync(); } catch (Exception ex) { throw ex; } }
複製程式碼

然後在控制檯中進行呼叫,如下:

        static void Main(string[] args)
        {
            AsyncVoidExceptions_CannotBeCaughtByCatch();
            Console.ReadKey();
        }

 

此時我們在非同步程式碼且返回值為void的方法中有一個異常,並且我們在呼叫該非同步方法中去捕捉異常,但是結果並未捕捉到。接下來我們將非同步方法返回值修改為Task如下再來看看:

複製程式碼
        private static async Task ThrowExceptionAsync()
        {
            
await Task.Delay(TimeSpan.FromSeconds(1)); throw new InvalidOperationException(); } private static async Task AsyncVoidExceptions_CannotBeCaughtByCatch() { try { await ThrowExceptionAsync(); } catch (Exception ex) { throw ex; } }
複製程式碼

 

此時發現返回值Task和void對於異常都無法捕捉到,這麼一來是不是返回值使用Task和void皆可以呢,我們注意到對於被呼叫的非同步方法且返回值為Task,我們試試將先接收其返回值,然後再await看看。此時我們對於第二個非同步方法修改成如下:

複製程式碼
        private static async Task AsyncVoidExceptions_CannotBeCaughtByCatch()
        {
            Task task = ThrowExceptionAsync();
            try
            {
                await task;
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
複製程式碼

 

通過事先接收其返回值Task然後再await,此時我們就能捕捉到異常,而為什麼void無法捕捉到異常呢?請看如下解釋

當在Task或者Task<T>中丟擲異常時,此時異常資訊將被捕捉到並被放到Task物件中,但是在void非同步方法啟動時SynchronizationContext將被啟用並且此時沒有Task物件,此時異常資訊將直接被儲存到非同步上下文中即(SynchronizationContext)。

對於捕捉void異常資訊其實沒有什麼根本上的解決辦法,如果是在控制檯中可以用下載 Nito.AsyncEx 程式包並將方法放在  AsyncContext.Run(()=>.....) 執行,還有其他等方法,返回值為void更多用在windows客戶端事件處理程式包中,例如如下:

複製程式碼
private async void btn_Click(object sender, EventArgs e)
{
  await BtnClickAsync();
}
public async Task BtnClickAsync()
{
  // Do asynchronous work.
  await Task.Delay(1000);
}
複製程式碼

在非同步操作中如果返回值為Task或者Task<T>,我們知道接下來給如何進行處理,但是返回值為void我們根本不知道它什麼時候完成,同時利用void來進行單元測試時也不會丟擲異常,所以我們對於非同步返回值大部分情況下必須使用Task或者Task<T>,除了基於事件處理而不得不返回void外,對於Task或者Task<T>有利於異常捕捉、暴露更多方法如(Task.WhenAll、Task.WhenAny)、方便單元測試等,基於此我們在此下一個基本結論:

雖然在非同步方法中提示返回值可以為Task、Task<T>或者void,但是我們強烈建議返回值只為Task或者Task<T>,除了基於事件處理程式外,因為返回值為void無法捕捉異常資訊且不方便單元測試,同時根本不知道非同步操作什麼時候完成。而對於Task異常資訊被儲存到Task物件中,所以在捕捉異常資訊時,首先返回非同步方法Task,然後進行await。

但是對於Task捕捉異常資訊還有一個問題我們並未探討,請往下看。

複製程式碼
        public static Task<int> First()
        {
            return Task<int>.Factory.StartNew(() =>
            {
                throw new Exception(" Exception From First!");
            });
        }
        public static Task<int> Second()
        {
            return Task<int>.Factory.StartNew(() =>
            {
                throw new Exception(" Exception From Second!");
            });
        }
複製程式碼

上述定義兩個非同步方法,並且都丟擲異常,接下來我們再來定義一個方法呼叫上述兩個方法,如下:

        public static async Task<int> Caclulate()
        {
            return await First() + await Second();
        } 

 

上述情況下理論上呼叫兩個方法應該丟擲兩個異常資訊才對,但是結果只對一個First非同步方法丟擲異常,而對於第二個非同步方法Second則忽略了,什麼情況,還沒看懂,我們進一步進行如下改造。

複製程式碼
        static void Main(string[] args)
        {
            try
            {
                Caclulate().Wait(1000);
            }
            catch (AggregateException ex)
            {

                throw ex;
            }
            Console.ReadKey();
         }
複製程式碼

我們通過聚合異常類 AggregateException 來接收異常資訊,結果只丟擲一個異常資訊,並且是第一個。 我們再利用返回Task來接收並await來看看是否有不同。

複製程式碼
        public static async Task Test()
        {
            var task = Caclulate();
            try
            {
                await task;
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
複製程式碼

此時也將僅僅丟擲第一個異常資訊,所以通過這裡演示我們可以下個結論:當在非同步程式碼中呼叫多個非同步方法時,若出現異常,此時則不會丟擲聚合異常而僅僅只是丟擲第一個異常。

建議非同步感染

在非同步操作中如果非同步程式碼又被其他非同步程式碼呼叫時,將同步程式碼轉換為非同步程式碼能夠更有效執行,在非同步程式碼中沒有感染的概念,為什麼我提出“感染”這一概念呢,想必正確使用過非同步方法的童鞋深有體會,當一個非同步方法被另外一個方法呼叫時,此時另外一個方法若是同步方法,此時會提示將該方法非同步,所以通過該傳播行為從最底層非同步方法到最高層呼叫者都將是非同步方法(類似殭屍屍毒),這也是我們所推薦的,一旦用了非同步程式碼則總是用非同步程式碼,不要將同步程式碼和非同步程式碼混合使用,很容易導致阻塞情況特別是呼叫Task.Wait或者Task.Result。這一點我有切身感受,在爬蟲中利用同步方法中呼叫非同步程式碼,最終獲取該非同步方法中的結果通過Task.Rsult,結果利用Windows窗體測試時發現已經被阻塞,一直顯示Task.Result處於計算中。不信,你看如下程式碼。所以我們強烈建議:一旦使用非同步程式碼且總是使用非同步程式碼讓非同步程式碼自然過渡層層傳遞,大部分情況下千萬別調用Task.Wait或者Task.Result很容易導致阻塞。

複製程式碼
    public static class DeadlockDemo
    {
        private static async Task DelayAsync()
        {
            await Task.Delay(1000);
        }
      
        public static void Test()
        {    
            var delayTask = DelayAsync();
            delayTask.Wait();
        }
    }
複製程式碼
        private void btn_click(object sender, EventArgs e)
        {
            DeadlockDemo.Test();
            MessageBox.Show("非同步死鎖");
        }

將上述程式碼在windows form或者ASP.NET程式中執行你會發現上述呼叫Wait後會導致死鎖,但在控制檯中將不會出現這種死鎖情況。按照我們對非同步的理解,預設情況下,當一個未被完成的任務被await時,此時將捕捉到當前上下文,直到任務被完成喚醒該方法,如果當前上下文為空,那麼此時當前上下文則為SynchronizationContext。對於如winddows form中的GUI或者ASP.NET應用程式,此時任務排程器的上下文則是SynchronizationContext且只允許一塊程式碼執行一次,當任務完成時,將試圖在捕捉的當前上下文去執行非同步方法中的其他方法,但是此時已經有一個執行緒當前上下文存在,造成同步方法去等待完成非同步方法,結果引起非同步方法喚醒當前方法繼續執行,但是當前同步方法也在等待非同步方法完成,彼此等待,造成死鎖。

建議非同步配置上下文(分情況)

什麼時候應該配置上下文,當我們需要等待結果完成時可以配置上下文,如下:

複製程式碼
        async Task ConfigureContext()
        {           
            await Task.Delay(1000);
          
            await Task.Delay(1000).ConfigureAwait(
              continueOnCapturedContext: false);
            
        }
複製程式碼

當進行如上配置後在 await Task.Delay(1000); 之前毫無疑問將在原始上下文中執行,  await Task.Delay(1000).ConfigureAwait( continueOnCapturedContext: false); 此時在此之後因為不捕捉上下文,此時將線上程池中執行。我們在此之前演示了一個造成死鎖的例子,通過配置上下文就可以解決。

複製程式碼
        private static async Task DelayAsync()
        {
            await Task.Delay(1000).ConfigureAwait(
    continueOnCapturedContext: false);
        }

        public static void Test()
        {
            var delayTask = DelayAsync();
            delayTask.Wait();
        }
複製程式碼

我們知道預設情況下當await一個未完成的任務時,此時將捕獲上下文來喚醒非同步方法來執行其餘的方法,但是此時我們配置上下文為false,告訴它不需要捕獲我們根本不耗費時間,我們馬上就能完成,此時將解決死鎖的問題。在非同步中配置 ConfigureAwait( continueOnCapturedContext: false); 的作用在於:將同步方法轉換為非同步方法和防止死鎖

那麼問題來了什麼時候不應該配置上下文呢?請繼續看如下例子:

複製程式碼
        private async void btn_click(object sender, EventArgs e)
        {
            Enabled = false;
            try
            {
                await Task.Delay(1000).ConfigureAwait(
    continueOnCapturedContext: false);
            }
            finally
            {

                Enabled = true;
            }

        }
複製程式碼

當點選按鈕時我們禁用按鈕,同時關閉了其捕獲當前上下文,但是最後我們又需要用到當前上下文,所以此時導致取不到一樣的執行緒,此時類似跨執行緒,出現執行緒不一致的情況。每個非同步方法都有其上下文並且每個方法的上下文是獨立開來的。什麼意思呢,由於上述我們直接在點選事件裡面關閉了捕獲上下文,如果我們定義一個方法,在此方法裡面來關閉捕獲上下文,此時再來在點選事件裡呼叫該非同步方法,此時點選事件和該非同步方法獨立互不影響,千萬別以為呼叫了該非同步方法就說明是在點選事件裡關閉了上下文,如下:

複製程式碼
        private async void btn_click(object sender, EventArgs e)
        {
            Enabled = false;
            try
            {
                await DisableBtnAsync();
            }
            finally
            {

                Enabled = true;
            }

        }

        private async Task DisableBtnAsync()
        {
           
            await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: 
                false);
        }
複製程式碼

 

由上已經證明了這點,好了本節我們到此結束。

相關推薦

關於Task一點思考建議

建議非同步返回Task或Task<T> 當在.NET Core中寫爬蟲用到非同步去下載資源後接下來進行處理,對於處理完成結果我返回void,想到這裡不僅僅一愣,這麼到底行不行,翻一翻寫的第一篇部落格,只是提醒了我下不要用void,至於為何不用也沒去探討,接下

25、生鮮電商平臺-原始碼地址公佈與思考建議

說明:今天是承諾給大家的最後一天,我公佈了github地址(QQ群裡面有)。誠然這個是我的計劃中的事情,但是有以下幾點思考請大家共勉:   1. 你下了那麼多的github程式碼,你學了多少呢?你又學會了多少呢?       github的確是21世紀IT研發人

關於程式碼版本管理的思考建議

      眾所周知,良好的系統研發應該有延續性和一致性,所以很多公司非常注意程式碼版本控制,並逐漸慢慢的迭代自己的產品。   具體到自然資源行業來說,我司純粹的產品銷售較少,專案開發較多,不同的地區,不同的客戶對於同一個功能的理解可以千差萬別。即

關於順序點,副作用賦值運算子的一點思考

《c primer plus》p104中講到: 副作用是對資料物件或檔案的修改 c的主要目的是對錶達式求值 c標準規定,在順序點,所有的副作用都在進入下一步前被計算(順序點是修改值的最晚時刻,有可能比它早)   3類順序點: 1.每個完整表示式結束後,即分號後面 2

關於搭建直播系統平臺的一點心得經驗建議

選擇 關於 產品 fps 高清 而在 必須 處理 就是 如今的直播發展如此迅猛,不管是短視頻APP還是購物APP都開通了直播功能,下面根據我個人的從業經驗講一下,希望和大家一起學習和提高。就直播的整個業務邏輯來說,主要分為“采集、前處理、編碼、傳輸、解碼、渲染”這幾個環節,

對glPushAttribglPopAttrib的一點思考

 先把今天遇到的問題描述下吧,本來有兩個影像圖層,我對第一個圖層設定了裁剪範圍,然後再繪製第二個圖層,此時第二個圖層不顯示,此問題僅出現在NVIDIA顯示卡上,AMD顯示卡正常,讓我鬱悶了好久。  後來通過glPushAttrib和glPopAttrib解決了此問題,在渲染前

關於繼承多型的一點思考

我們知道繼承和多型是Java的三大特性中的兩個.今天正好有時間來探索繼承和多型的技術細節, 父類 public class Dad { public int age=54; public String name="jack"; publ

對分散式儲存平行計算的一點思考

分散式儲存: 首先是檔案在HDFS上面以128M塊大小儲存(3份),這三塊是在不同節點的(機架感知),我覺的好處是容錯還有當計算是這個節點資源不夠可以去塊所在的另一節點執行,不用拉取資料。 可以通過fs.getfileblocklocation()獲取塊位置 平行計算: 1、MR使用預設的輸

關於MVC,MVP,MVVM的一點總結思考

##簡介 軟體的架構方式有很多種,從最開始的MVC模式,演化到MVP,然後到現在的MVVM,在不斷的演化過程中其核心的思想就是降低各元件之間的耦合度,使得資料的流向更加的清晰明瞭。但並不是意味著一個比另一個高階,只是對於軟體的架構方式有的不同的視角,針對不同的場

關於測試職業發展的一點探討思考

最近在和團隊成員一對一溝通的時候,聊到比較深入的地方,得知大家有一個擔憂和想法:擔心現在工作上的積累,包括對業務的深入瞭解和產品相關技術的深入學習,以後到了新的專案上可能不適用,因而自己會變得沒有競爭

關於會計對賬的一點總結思考

會計學有一個很重要和很出名的公式,相信很多人都見過: 資產 = 負債 + 所有者權益 + 收入 – 費用         但會計學上還有另外一個比較隱蔽的公式,雖然不像上面的公式那麼出名,但也經常能見到它(或者變種運用)的身影 那就是,即期末值=期初值+期間變化值,也就是

對TCP埠連線數的一點思考

先來看看一些約定俗成的內容。 一個網絡卡對應一個IP地址 一個IP地址對應65535個埠 一個socket(addr, port)可以接受多個socket連線(accept) 一個埠只能被一個socket監聽(listen)

設計模式的一點總結思考(一)建立型

面向介面程式設計 對於當前不知道或無法確定的東西,我們就抽象它,只對其介面操作,即現在不知道具體的涉及物件,但我知道如何使用它,先用其介面,待以後知道了具體的物件之後,再繫結上即可,這就是所謂的封裝變化。 雖然不確定目標是誰,但可以確定如何使用目標。 多種

關於雄安新區的一點觀察思考

今天各大網站上都被關於國家建設雄安新區的新聞佔據這,所以到網上搜了一下雄安新區的位置,也就是(1)所示的位置,將北京,天津和石家莊連線成三角形, 可以比較清楚的看出,雄安新區,位於這三者的一條垂直平分

關於使用原生Javascript的一點思考

jquery script 打開 效應 ani 思維 速度 java 我想 前端很大,大到那些連類都不知道的僅僅上了培訓班的人都會用jquery寫網頁。。。 當一個行業的從業人數出現爆發性增長時,基於規模效應,真正有用的有思想的技術就會出現。。。 這也是我推崇用原生

一點思考

他能 輸出 視野 事情 好奇心 3年 態度 來看 解決 1.很多事,有可能,我們現在看是對的,將來看是錯的,3年很短,但是三十年很長,足夠我們做很多事情,目光長遠一點,視野開闊一點。2.保持一顆好奇心,好奇心,能讓你更細致的觀察,感受這個世界,發現問題、缺陷、美。3.世界上

關於ugc的一點思考

創作 微博 ugc 一個 一點 nbsp 多人 關於 更多 ugc會使互聯網繁榮,但依賴大眾用戶創造的內容質量上會存在參差不齊,這是ugc本身存在的問題。 就拿技術論壇或社區來說,好的內容不少,但質量不好的內容也很多。社區在引導用戶發言的同時,也應

個性化推薦系統(三)---推薦系統意義一點思考

進展 這樣的 es2017 意見 推廣 移動 付出 技術 com 個性化推薦是隨著移動互聯網發展不斷發展起來的,國內應用個性化推薦技術最早應該是豆瓣,在web2.0興起時做了很多嘗試,給網民帶來很多新鮮感覺、體驗。後來是國外電影租賃網站netflex推波助瀾

最近的一點思考,關於高手/大師/學霸

一點 現在 考試 比較 吐槽 垃圾 一切都 離開 pan 最近重新學習線代,微積分,概率論。大學的時候就沒有好好學過,覺得概念,公式太多,而且很多難以理解。 而我自己一直認為數學能力還是不錯的。但是到大學以後,覺得完全跟不上,和邊上的人交流,包括學生和工作以後的人交流,完全

對C#調用C++的dll的一點思考

今天 def byte lag har 調用 一段時間 總結 unsigned   最近在對接C++程序的時候碰到了一些問題,然後花了一段時間才解決,今天就這些小問題來做一個總結,很多時候由於對另外一種開發語言的不熟悉,會在使用的過程中遇到很多的問題,這些問題看似簡單但是背