同步請求資源
請求msdn上的一個頁面計算頁面大小

static void Main(string[] args)
{
string url = "https://docs.microsoft.com/zh-cn/dotnet/core/api/system";
try
{
WebRequest request = WebRequest.Create(url);
WebResponse response = request.GetResponse();
using (var reader = new StreamReader(response.GetResponseStream()))
{
string text = reader.ReadToEnd();
Console.WriteLine(FormatBytes(text.Length));
}
}
catch (WebException e)
{
}
catch (IOException e)
{
}
catch (NotSupportedException e)
{
}
}
static string FormatBytes(long bytes)
{
string[] magnitudes = new string[] { "GB", "MB", "KB", "Bytes" };
long max = (long)Math.Pow(1024, magnitudes.Length);
var t1 = magnitudes.FirstOrDefault(magnitude => bytes > (max /= 1024)) ?? "0 Bytes";
var t2 = ((decimal)bytes / (decimal)max);
return string.Format("{1:##.##} {0}", t1, t2).Trim();
}

Ctrl+F5輸出
閃爍兩下後
這裡對資源的請求都是同步的,通俗易懂點就是一個步驟一個步驟的執行,任何一個步驟耗時較長都會阻塞上下文線程(這裡就是主線程)
使用C#5.0異步請求資源

static void Main(string[] args)
{
string url = "https://docs.microsoft.com/zh-cn/dotnet/core/api/system";
Task task = WriteWebRequestSizeAsync(url);
while (!task.Wait(100))
{
Console.Write(".");
}
}
static async Task WriteWebRequestSizeAsync(string url)
{
try
{
WebRequest request = WebRequest.Create(url);
WebResponse response = await request.GetResponseAsync();
using (var reader = new StreamReader(response.GetResponseStream()))
{
string text = await reader.ReadToEndAsync();
Console.WriteLine(FormatBytes(text.Length));
}
}
catch (WebException)
{
}
//省略了一些catch塊
}

這種寫法在MVC中早就熟悉了,但是原理确不是很清楚,隻知道這樣不會阻塞UI,async方法會建立一個新的線程執行,await會阻塞上下文線程,一知半解隐藏着很可怕定時炸彈!
TPL異步調用

static void Main(string[] args)
{
string url = "https://docs.microsoft.com/zh-cn/dotnet/core/api/system";
Task task = WriteWebRequestSizeAsync(url);
try
{
while (!task.Wait(100))
{
Console.Write(".");
}
}
catch (AggregateException excetion)
{
excetion = excetion.Flatten();
try
{
excetion.Handle(innerExcetion =>
{
ExceptionDispatchInfo.Capture(excetion.InnerException).Throw();
return true;
});
}
catch (WebException ex)
{
}
//省略了一些catch塊
}
}
static Task WriteWebRequestSizeAsync(string url)
{
StreamReader reader = null;
WebRequest request = WebRequest.Create(url);
Task task = request.GetResponseAsync()
.ContinueWith(antecedent =>
{
WebResponse response = antecedent.Result;
reader = new StreamReader(response.GetResponseStream());
return reader.ReadToEndAsync();
})
.Unwrap()
.ContinueWith(antecedent =>
{
if (null != reader)
reader.Dispose();
string text = antecedent.Result;
Console.WriteLine(FormatBytes(text.Length));
});
return task;
}

這個寫法是在沒有C#5.0之前,異步請求資源就是這麼完成的,乍一看非常複雜,但是比較一下上面一種寫法,它似乎是思路清晰的
1.request.GetResponseAsync建立一個任務等待msdn伺服器的響應
2.拿到這個響應後,獲得響應流,将流轉為字元串
3.接下來是Unwrap,這個應該是最難了解的了,實際上隻有加上這句話以ContinueWith的思路寫下去,下一步就是直接拿string了
4.最後流已經轉為字元串了,我們做個簡單的計算就行了
Unwrap的奧義
public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task);
從簽名上來看,它是一個Task<Task<TResult>>類型的拓展方法,任務,帶傳回值的任務...暈了,别急,等下慢慢來
回到剛剛代碼看看有什麼端倪
1.GetResponseAsync()建立(意思和傳回值一樣,為了區分任務傳回值)一個任務,具體類型Task<WebResponse>,這個任務結束傳回一個WebResponse
2.第一個ContinueWith調用者是一個Task<WebResponse>,形參是一個委托接受一個Task<WebReponse>,傳回一個Task<string>(通過reader.ReadToEndAsync可以了解),傳回值是一個Task沒有問題,問題在它的泛型參數是什麼,首先此處ContinueWith中的代碼是在一個新的工作線程中運作的,我們把它想象成主線程(隻是相對的一個環境),'主線程'要完成什麼任務呢?,他要拿Task<WebResponse>的執行結果WebResponse(雖然這裡可以肯定這個任務已經執行完成了,但是微軟沒有這麼做),然後根據這個WebResponse在建立一個Task<string>,作為目前工作線程的傳回值
3.現在問題來了,我們得到的是一個Task<Task<string>>,我們可以通過調用兩次Result擷取這個string,但是在這個ContinueWith塊中,隻能保證外層的Task是執行完成的,是以第二個Result或阻塞上下文線程

Task task = request.GetResponseAsync()
.ContinueWith(antecedent =>
{
WebResponse response = antecedent.Result;
reader = new StreamReader(response.GetResponseStream());
return reader.ReadToEndAsync();
})
.ContinueWith(antecedent =>
{
var resTask = antecedent.Result;
var resString = resTask.Result;
if (null != reader)
{
string text = resString;
Console.WriteLine(FormatBytes(text.Length));
}
});

4.這個時候再回到Unwarp(),它就是脫去了外層的Task,得到的内層的任務上下文線程,并把它作為延續任務的主線程
5.這裡取出的Result就是string,計算并輸出
法器ILSpy
通過ILSpy反編譯後可以逐漸查到,Unwarp()實際上就是保證了内層任務開始執行,并傳回一個内層任務的執行環境(上下文線程)
分類: C#
标簽: 多線程