當我們在進行電腦資源存取的時候,例如:檔案、資料庫、網路、記憶體、系統資源等等,需要特別小心操作,最保險的方式就是將這些存取資源的敘述,使用 try 將這些敘述包含在內,這樣,當有任何例外異常發生的時候,就可以捕捉到這些例外異常問題。
可是,有個問題是比較棘手的,那就是發生逾期的問題,在我們進行 HttpClient 的相關請求操作的時候,也許是因為網路品質、斷線、伺服器的效能或者後端程式碼撰寫品質不佳影響,會造成用戶端長時間無法得到結果。
若我們使用 HttpClient 的時候,若沒有特別指定
HttpClient.Timeout
屬性值, 他的預設逾期時間.aspx)為 100 秒,這樣的時間,對於一般的程式應用似乎有點過長,因此,在這篇文章中,我們將來學習 HttpClient 逾期的程式碼設計方式。
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
呼叫 Web API 的逾時時間限制用法
在我們所設計的逾期 Web API,只要您呼叫這個 Web API,他將會延遲5秒鐘之後,才會將結果回傳到用戶端;因此,在
JsonPutAsync
方法中,我們將會接受到一個秒數參數,讓我們可以設定 HttpClient 的逾期時間。我們使用這樣的敘述 client.Timeout = TimeSpan.FromSeconds(sec);
來設定這個 HttpClient 各種操作的最大逾期時間。
若進行 HttpClient 的網路存取操作的時候,若得到回傳結果的時間,大於這裡設定的逾期時間,這個時候 HttpClient 物件將會丟出例外異常,底下是這時間逾期的呼叫堆疊範例。
於 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
於 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
於 System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext()
--- 先前擲回例外狀況之位置中的堆疊追蹤結尾 ---
於 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
於 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
於 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
於 LongTimeAccess.Program.<JsonPutAsync>d__1.MoveNext() 於 D:\Vulcan\GitHub\CSharpNotes\WebAPI\LongTimeAccess\Program.cs: 行 77
而這個時間逾期的例外異常,其 Message 屬性將會得到
工作已取消。
的字串內容。
若您有參考這份 HttpClient 使用文章中的寫法,您將會發現到,我們有將 HttpClient 的所有存取動作,都使用 Try 包裹起來,也就是說,我們可以捕捉到任何 HttpClient 所發出的例外異常。
private static async Task<APIResult> JsonPutAsync(int sec = 4)
{
APIResult fooAPIResult;
using (HttpClientHandler handler = new HttpClientHandler())
{
using (HttpClient client = new HttpClient(handler))
{
try
{
client.Timeout = TimeSpan.FromSeconds(sec);
#region 呼叫遠端 Web API
string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/values";
HttpResponseMessage response = null;
#region 設定相關網址內容
var fooFullUrl = $"{FooUrl}/LongTimeGet";
// Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用)
//client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Content-Type 用於宣告遞送給對方的文件型態
//client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
response = await client.GetAsync(fooFullUrl);
#endregion
#endregion
#region 處理呼叫完成 Web API 之後的回報結果
if (response != null)
{
if (response.IsSuccessStatusCode == true)
{
// 取得呼叫完成 API 後的回報內容
String strResult = await response.Content.ReadAsStringAsync();
fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
}
else
{
fooAPIResult = new APIResult
{
Success = false,
Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.RequestMessage),
Payload = null,
};
}
}
else
{
fooAPIResult = new APIResult
{
Success = false,
Message = "應用程式呼叫 API 發生異常",
Payload = null,
};
}
#endregion
}
catch (Exception ex)
{
fooAPIResult = new APIResult
{
Success = false,
Message = ex.Message,
Payload = ex,
};
}
}
}
return fooAPIResult;
}
觸發的 Web API 動作
這個範例中,將會指向 URL
http://vulcanwebapi.azurewebsites.net/api/values/LongTimeGet
,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public async Task<APIResult> LongTimeGet()
動作(Action),其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpGet("LongTimeGet")]
public async Task<APIResult> LongTimeGet()
{
APIResult foo;
await Task.Delay(5000);
foo = new APIResult()
{
Success = true,
Message = "透過 Get 方法",
Payload = new APIData()
{
Id = 777,
Name = "Vulcan01"
}
};
return foo;
}
進行測試
在程式進入點函式,我們先設定要使用 4 秒的逾期時間設定(伺服器端所指定的 Web API 動作,需要花費至少 5 秒的時間,才會回傳到用戶端上),因此,我們將會得到這樣失敗的結果;接著,我們將用戶端的逾期時間設定為 6 秒鐘,結果是可以順利得到遠端 Web API 的回傳結果。。
static void Main(string[] args)
{
Console.WriteLine("遠端 Web API 需要花費 5 秒鐘,才會回傳結果內容");
Console.WriteLine($"使用 Get 方法呼叫 Web API ,並且會逾期的結果 ( HttpClient 限時 4 秒內要完成 )");
var foo = JsonPutAsync(4).Result;
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
if (foo.Success == true)
{
var item = JsonConvert.DeserializeObject<APIData>(foo.Payload.ToString());
Console.WriteLine($"Id : {item.Id}");
Console.WriteLine($"Name : {item.Name}");
Console.WriteLine($"Filename : {item.Filename}");
}
Console.WriteLine($"");
Console.WriteLine($"Press any key to Continue...{Environment.NewLine}");
Console.ReadKey();
Console.WriteLine($"使用 Get 方法呼叫 Web API ,並且不會逾期的結果 ( HttpClient 限時 6 秒內要完成 )");
foo = JsonPutAsync(6).Result;
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
if (foo.Success == true)
{
var item = JsonConvert.DeserializeObject<APIData>(foo.Payload.ToString());
Console.WriteLine($"Id : {item.Id}");
Console.WriteLine($"Name : {item.Name}");
Console.WriteLine($"Filename : {item.Filename}");
}
Console.WriteLine($"");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
執行結果
這個測試將會輸出底下內容
遠端 Web API 需要花費 5 秒鐘,才會回傳結果內容
使用 Get 方法呼叫 Web API ,並且會逾期的結果 ( HttpClient 限時 4 秒內要完成 )
結果狀態 : False
結果訊息 : 工作已取消。
Press any key to Continue...
使用 Get 方法呼叫 Web API ,並且不會逾期的結果 ( HttpClient 限時 6 秒內要完成 )
結果狀態 : True
結果訊息 : 透過 Get 方法
Id : 777
Name : Vulcan01
Filename :
Press any key to Exist...
HTTP 傳送與接收原始封包
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
- 請求 (Request)首先,我們發出一個 GET 請求,底下是一般的 Http GET 請求的封包內容。
GET http://vulcanwebapi.azurewebsites.net/api/values/LongTimeGet HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
- 反應 (Response)底下是使用 Fiddler 側錄下 Http 封包的內容,雖然 C# 用戶端因為逾期時間為 4 秒,沒有在 5 秒後收到回傳結果,而在 C# 用戶端產生了例外異常,不過,伺服器還是會在 5 秒後將結果回傳回來(此時,C# 用戶端程式已經終止執行了),不過, Fiddler 還是有側錄下來。
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Mon, 23 Oct 2017 02:16:31 GMT
65
{"success":true,"message":"透過 Get 方法","payload":{"id":777,"name":"Vulcan01","filename":null}}
0
- 請求 (Request)在這裡的第一行中,您將會看到了完整有查詢字串 (Query String) 的 URL,在問號之後的內容,將會傳送到後端 Web API 動作函式的參數內,此時的 Id值為 777。
GET http://vulcanwebapi.azurewebsites.net/api/values/LongTimeGet HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
- 反應 (Response)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Mon, 23 Oct 2017 02:16:40 GMT
65
{"success":true,"message":"透過 Get 方法","payload":{"id":777,"name":"Vulcan01","filename":null}}
0
相關文章索引
C# HttpClient WebAPI 系列文章索引
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
關於 Xamarin 在台灣的學習技術資源
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程