並發系列64章(非同步編程)第二章
- 2020 年 4 月 7 日
- 筆記
前言
非同步編程的概念我在第一章概要的時候,提及了。在此再次簡略概要一次。
它採用future模式或者回調模式機制,以避免產生不必要的執行緒。
非同步編程測試的標準
在第一個寫這個的原因,是因為測試可能比開發重要。因為在開發一個項目的時候呢?有一個自動化高效精準測試,決定了上線是否穩定。因為程式出bug測試出來可以改,方案不行換方案,但是測試不行上線了。這時候面臨的問題就比較大,因為這時候產生了數據。
比如說 app 一張表的設計不合理,在自動化測試中沒有體現出來,那麼你要更換表的時候就顯得異常困難,這時候到底換不換表的結構呢?換了之後,如何兼容之前的版本?迭代的方案是啥。好的,扯得很遠了。
當然我們作為開發人員也要做好單元測試,及子系統測試。好的,近了一點了。
我們在寫一個非同步程式的時候,是有3個測試必須通過。
1.同步成功
2.非同步成功
3.非同步失敗
先介紹一下如何非同步測試:
public static async Task<T> DelayResult<T>(T result, TimeSpan delay) { await Task.Delay(delay); return result; }
如何測試的時候如果這樣寫:
[Fact] public async void Test1() { TimeSpan timeSpan = new TimeSpan(); Program.DelayResult<int>(1, timeSpan); }
那麼這個測試是有問題的。
比如:
public static async Task<T> DelayResult<T>(T result, TimeSpan delay) { await Task.Delay(delay); throw new Exception("error"); return result; }
本來我是應該拋出異常的,但是:
結果是下面這樣的。
原因就涉及到一個異常捕獲的問題了,可以查詢一下原理。
運行測試的時候應該加上await:
[Fact] public async void Test1() { TimeSpan timeSpan = new TimeSpan(); await Program.DelayResult<int>(1, timeSpan); }
那麼這個時候就可以捕獲到異常。
下面介紹一些例子。
指數退避
這個是什麼意思呢?比如說,我們訪問我們的一條url的時候,訪問失敗。
接下來我們應該做的是重試,那麼是否馬上重試?不是的,除非是阻塞式的api調用,例如登錄。
但是呢,如果不是阻塞式的,那麼應該把資源分配均衡。因為你一次失敗,第二次的也有可能失敗。
那麼這時候指數退避是一種良好的方法。
static async Task<string> visitUrl(string url) { using (var client = new HttpClient()) { var nextDelay = TimeSpan.FromSeconds(1); for (int i = 0; i != 3; ++i) { try { return await client.GetStringAsync(url); } catch { } await Task.Delay(nextDelay); nextDelay = nextDelay + nextDelay; } // 返回最後的結果方便得出錯誤 return await client.GetStringAsync(url); } }
測試:
[Fact] public async void Test1() { await Program.visitUrl("www.xxx.com"); }
結果:
測試花了7秒。
正確驗證測試我就不測了。
實現超時功能
上面的這個程式碼,我們發現一個問題啊,如果訪問那個鏈接要好久,那麼這也很受傷啊。
是否能加入一個超時,如果訪問一段時間沒有返回結果,那麼把資源留給別的需求者。
public static async Task<string> visitTimeoutUrl(HttpClient client,string url) { var visitTask=client.GetStringAsync(url); var timeoutTask = Task.Delay(3000); var completedTask = await Task.WhenAny(visitTask,timeoutTask); if (completedTask == timeoutTask) { return null; } return await visitTask; }
上文實現了一個簡單的超時。
然後改一下:
public static async Task<string> visitUrl(string url) { using (var client = new HttpClient()) { var nextDelay = TimeSpan.FromSeconds(1); for (int i = 0; i != 3; ++i) { try { var result= await visitTimeoutUrl(client,url); if (result != null) { return result; } } catch { } await Task.Delay(nextDelay); nextDelay = nextDelay + nextDelay; } // 返回最後的結果方便得出錯誤 return await visitTimeoutUrl(client, url); } }
未完
今天寫部落格的時候,一直出現error,就先到這吧。
下一章,還是幾個例子感受一下。以上為個人理解,如有不對望請指出。