天天看點

同步 VS 異步

同步請求資源

請求msdn上的一個頁面計算頁面大小

同步 VS 異步
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();
}      
同步 VS 異步

Ctrl+F5輸出 

同步 VS 異步

閃爍兩下後

同步 VS 異步

這裡對資源的請求都是同步的,通俗易懂點就是一個步驟一個步驟的執行,任何一個步驟耗時較長都會阻塞上下文線程(這裡就是主線程)

使用C#5.0異步請求資源

同步 VS 異步
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塊
}      
同步 VS 異步

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

TPL異步調用

同步 VS 異步
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;
}      
同步 VS 異步

這個寫法是在沒有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

同步 VS 異步

 2.第一個ContinueWith調用者是一個Task<WebResponse>,形參是一個委托接受一個Task<WebReponse>,傳回一個Task<string>(通過reader.ReadToEndAsync可以了解),傳回值是一個Task沒有問題,問題在它的泛型參數是什麼,首先此處ContinueWith中的代碼是在一個新的工作線程中運作的,我們把它想象成主線程(隻是相對的一個環境),'主線程'要完成什麼任務呢?,他要拿Task<WebResponse>的執行結果WebResponse(雖然這裡可以肯定這個任務已經執行完成了,但是微軟沒有這麼做),然後根據這個WebResponse在建立一個Task<string>,作為目前工作線程的傳回值

同步 VS 異步

 3.現在問題來了,我們得到的是一個Task<Task<string>>,我們可以通過調用兩次Result擷取這個string,但是在這個ContinueWith塊中,隻能保證外層的Task是執行完成的,是以第二個Result或阻塞上下文線程

同步 VS 異步
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));
        }
    });      
同步 VS 異步

 4.這個時候再回到Unwarp(),它就是脫去了外層的Task,得到的内層的任務上下文線程,并把它作為延續任務的主線程

同步 VS 異步

 5.這裡取出的Result就是string,計算并輸出

同步 VS 異步

法器ILSpy

通過ILSpy反編譯後可以逐漸查到,Unwarp()實際上就是保證了内層任務開始執行,并傳回一個内層任務的執行環境(上下文線程)

同步 VS 異步
同步 VS 異步

分類: C#

标簽: 多線程

繼續閱讀