並發系列64章(非同步編程)第二章

前言

非同步編程的概念我在第一章概要的時候,提及了。在此再次簡略概要一次。

它採用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,就先到這吧。
下一章,還是幾個例子感受一下。以上為個人理解,如有不對望請指出。