c# 異步編程

原文連接:http://www.javashuo.com/article/p-dscnbtvz-me.htmlhtml

前言
C#異步編程有幾種實現方式,異步方法就是其中的一種。異步方法是 C#5.0 纔有的新特性,主要採用 async、await 關鍵字聲明爲異步方法,完成對方法的異步調用。C#5.0 對應的 VS 版本是 VS2012,對應的 .NET Framework 版本是 v4.5,因此須要在此基礎上才支持。(不然可能報:找不到「async」修飾符所需的全部類型。目標框架版本是否不正確,或者缺乏對程序集的引用?)編程

什麼是異步方法
1. 異步方法,是指在執行當前方法的同時,能夠異步的去調用其餘方法(異步方法),而且不會阻塞當前方法的線程。
2. 使用了 async 修飾符的方法稱爲異步方法,一般配合 await 運算符和 Task 異步任務一塊兒使用。
1) 若是方法使用了 async 修飾符,則方法中須要包含一個以上 await 運算符,不然將以同步執行。
2) 反之,若是方法中包含一個以上 await 運算符,則必須聲明爲一個異步方法,即便用 async 修飾符。app

3. Task 分爲兩種:
1) Task,表示能夠執行一個異步操做,聲明以下:
public class Task : IAsyncResult, IDisposable { }
2) Task<TResult>,表示能夠執行帶有返回值的異步操做,聲明以下:
public class Task<TResult> : Task { }框架

4. 異步方法的返回類型必須爲 void、Task、Task<TResult> 中的其中一種。
1) void,表示無返回值,不關心異步方法執行後的結果,通常用於僅僅執行某一項任務,可是不關心結果的場景。
2) Task,表示異步方法將返回一個 Task 對象,該對象一般用於判斷異步任務是否已經完成,可使用 taskObj.Wait() 方法等待,或者 taskObj.IsCompleted 判斷。
3) Task<TResult>,表示異步方法將返回一個 Task<TResult> 對象,該對象的 Result 屬性則是異步方法的執行結果,調用該屬性時將阻塞當前線程(異步方法未執行完成時)。dom

概括一下:void 不關心結果;Task 只關心是否執行完成;Task<TResult> 不止關心是否執行完成,還要獲取執行結果。

下面經過幾個生活中比較形象的例子來理解異步方法的使用
1. 模擬扔垃圾(不關心結果,返回 void 類型)異步

/// <summary>
/// 扔垃圾
/// </summary>
public void DropLitter()
{
    Console.WriteLine("老婆開始打掃房間,線程Id爲:{0}", GetThreadId());
    Console.WriteLine("垃圾滿了,快去扔垃圾");
    CommandDropLitter();
    Console.WriteLine("無論他繼續打掃,線程Id爲:{0}", GetThreadId());
    Thread.Sleep(100);
    Console.WriteLine("老婆把房間打掃好了,線程Id爲:{0}", GetThreadId());
}
 
/// <summary>
/// 通知我去扔垃圾
/// </summary>
public async void CommandDropLitter()
{
    Console.WriteLine("這時我準備去扔垃圾,線程Id爲:{0}", GetThreadId());
    await Task.Run(() =>
    {
        Console.WriteLine("屁顛屁顛的去扔垃圾,線程Id爲:{0}", GetThreadId());
        Thread.Sleep(1000);
    });
    Console.WriteLine("垃圾扔了還有啥吩咐,線程Id爲:{0}", GetThreadId());
}

運行以上代碼:async

以上代碼在 CommandDropLitter() 方法上加了 async 修飾符,而且使用 await 運算符開啓了一個新的 Task 去執行另外一個任務。注意:當前線程遇到 await 時,則馬上跳回調用方法繼續往下執行。而 Task 執行完成以後將執行 await 以後的代碼,而且與 await 以前的線程不是同一個。異步編程

2.模擬打開電源開關(關心是否執行完成,返回 Task 類型)this

/// <summary>
/// 打開電源開關
/// </summary>
public void OpenMainsSwitch()
{
    Console.WriteLine("我和老婆正在看電視,線程Id爲:{0}", GetThreadId());
    Console.WriteLine("忽然停電了,快去看下是否是跳閘了");
    Task task = CommandOpenMainsSwitch();
    Console.WriteLine("沒電了先玩會兒手機吧,線程Id爲:{0}", GetThreadId());
    Thread.Sleep(100);
    Console.WriteLine("手機也沒電了只等電源打開,線程Id爲:{0}", GetThreadId());
 
    //task.Wait();    //因此這裏將被阻塞,直到任務完成
    //或者
    while (!task.IsCompleted) { Thread.Sleep(100); }
 
    Console.WriteLine("又有電了咱們繼續看電視,線程Id爲:{0}", GetThreadId());
}
 
/// <summary>
/// 通知我去打開電源開關
/// </summary>
public async Task CommandOpenMainsSwitch()
{
    Console.WriteLine("這時我準備去打開電源開關,線程Id爲:{0}", GetThreadId());
    await Task.Run(() =>
    {
        Console.WriteLine("屁顛屁顛的去打開電源開關,線程Id爲:{0}", GetThreadId());
        Thread.Sleep(1000);
    });
 
    Console.WriteLine("電源開關打開了,線程Id爲:{0}", GetThreadId());
}

運行以上代碼:spa

1) 可見,調用 Wait() 方法後,當前線程被阻塞了,直到 Task 執行完成後,當前線程才繼續執行。
2) 注意:因爲 CommandOpenMainsSwitch() 是一個異步方法,雖然返回類型爲 Task 類型,可是在咱們代碼中並無寫(也不能寫) return task 語句,這是爲何呢?多是這種返回類型比較特殊,或者編譯器自動幫咱們完成了吧!就算寫也只能寫 return 語句,後面不能跟對象表達式。

3. 模擬去買鹽(不止關心是否執行完成,還要獲取執行結果。返回 Task<TResult> 類型)

/// <summary>
/// 作飯
/// </summary>
public void CookDinner()
{
    Console.WriteLine("老婆開始作飯,線程Id爲:{0}", GetThreadId());
    Console.WriteLine("哎呀,沒鹽了");
    Task<string> task = CommandBuySalt();
    Console.WriteLine("無論他繼續炒菜,線程Id爲:{0}", GetThreadId());
    Thread.Sleep(100);
    string result = task.Result;    //必需要用鹽了,等我把鹽回來(中止炒菜(阻塞線程))
    Console.WriteLine("用了鹽炒的菜就是好吃【{0}】,線程Id爲:{1}", result, GetThreadId());
    Console.WriteLine("老婆把飯作好了,線程Id爲:{0}", GetThreadId());
}
 
/// <summary>
/// 通知我去買鹽
/// </summary>
public async Task<string> CommandBuySalt()
{
    Console.WriteLine("這時我準備去買鹽了,線程Id爲:{0}", GetThreadId());
 
    string result = await Task.Run(() =>
    {
        Console.WriteLine("屁顛屁顛的去買鹽,線程Id爲:{0}", GetThreadId());
        Thread.Sleep(1000);
        return "鹽買回來了,順便我還買了一包煙";
 
    });
 
    Console.WriteLine("{0},線程Id爲:{1}", result, GetThreadId());
 
    return result;
}

運行以上代碼:

1) 以上代碼 task.Result 會阻塞當前線程,與 task.Wait() 相似。
2) 注意:與前面返回類型爲 Task 的 CommandOpenMainsSwitch() 方法同樣,雖然 CommandBuySalt() 方法返回類型爲 Task<string>,可是咱們的返回語句是 return 字符串。

其餘示例
1. 在前面(模擬去買鹽)的示例中,異步方法中只開啓了一個 Task,若是開啓多個 Task 又是什麼狀況,看代碼:

public void AsyncTest()
{
    Console.WriteLine("AsyncTest() 方法開始執行,線程Id爲:{0}", GetThreadId());
    Task task = Test1();
    Console.WriteLine("AsyncTest() 方法繼續執行,線程Id爲:{0}", GetThreadId());
    task.Wait();
    Console.WriteLine("AsyncTest() 方法結束執行,線程Id爲:{0}", GetThreadId());
}
 
public async Task Test1()
{
    Console.WriteLine("Test1() 方法開始執行,線程Id爲:{0}", GetThreadId());
    await Task.Factory.StartNew((state) =>
    {
        Console.WriteLine("Test1() 方法中的 {0} 開始執行,線程Id爲:{1}", state, GetThreadId());
        Thread.Sleep(1000);
        Console.WriteLine("Test1() 方法中的 {0} 結束執行,線程Id爲:{1}", state, GetThreadId());
    }, "task1");
 
    await Task.Factory.StartNew((state) =>
    {
        Console.WriteLine("Test1() 方法中的 {0} 開始執行,線程Id爲:{1}", state, GetThreadId());
        Thread.Sleep(3000);
        Console.WriteLine("Test1() 方法中的 {0} 結束執行,線程Id爲:{1}", state, GetThreadId());
    }, "task2");
 
    Console.WriteLine("Test1() 方法結束執行,線程Id爲:{0}", GetThreadId());
}

運行以上代碼:

當異步方法中有多個 await 時,會依次執行全部的 Task,只有當全部 Task 執行完成後才表示異步方法執行完成,當前線程才得以執行。

2. 一樣之前面(模擬去買鹽)的示例,若是發現其實家裏還有鹽,這是就要告訴我不用買了(取消異步操做),怎麼實現?這就要藉助 System.Threading.CancellationTokenSource 和 System.Threading.Tasks.CancellationToken 對象來完成。

/// <summary>
/// 作飯(買鹽任務取消)
/// </summary>
public void CookDinner_CancelBuySalt()
{
    Console.WriteLine("老婆開始作飯,線程Id爲:{0}", GetThreadId());
    Console.WriteLine("哎呀,沒鹽了");
    CancellationTokenSource source = new CancellationTokenSource();
    Task<string> task = CommandBuySalt_CancelBuySalt(source.Token);
    Console.WriteLine("無論他繼續炒菜,線程Id爲:{0}", GetThreadId());
    Thread.Sleep(100);
 
    string result = "家裏的鹽";
    if (!string.IsNullOrEmpty(result))
    {
        source.Cancel();    //傳達取消請求
        Console.WriteLine("家裏還有鹽不用買啦,線程Id爲:{0}", GetThreadId());
    }
    else
    {
        //若是已取消就不能再得到結果了(不然將拋出 System.Threading.Tasks.TaskCanceledException 異常)
        //你都叫我不要買了,我拿什麼給你?
        result = task.Result;
    }
 
    Console.WriteLine("既然有鹽我就繼續炒菜【{0}】,線程Id爲:{1}", result, GetThreadId());
    Console.WriteLine("老婆把飯作好了,線程Id爲:{0}", GetThreadId());
    Console.WriteLine("最終的任務狀態是:{0},已完成:{1},已取消:{2},已失敗:{3}",
        task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
}
 
/// <summary>
/// 通知我去買鹽(又告訴我不用買了)
/// </summary>
public async Task<string> CommandBuySalt_CancelBuySalt(CancellationToken token)
{
    Console.WriteLine("這時我準備去買鹽了,線程Id爲:{0}", GetThreadId());
 
    //已開始執行的任務不能被取消
    string result = await Task.Run(() =>
    {
        Console.WriteLine("屁顛屁顛的去買鹽,線程Id爲:{0}", GetThreadId());
        Thread.Sleep(1000);
    }, token).ContinueWith((t) =>  //若沒有取消就繼續執行
    {
        Console.WriteLine("鹽已經買好了,線程Id爲:{0}", GetThreadId());
        Thread.Sleep(1000);
 
        return "鹽買回來了,順便我還買了一包煙";
    }, token);
 
    Console.WriteLine("{0},線程Id爲:{1}", result, GetThreadId());
 
    return result;
}

運行以上代碼:

1) 剛開始我覺得調用 source.Cancel() 方法後會當即取消 Task 的執行,仔細一想也不太可能。若是須要在 Task 執行前或者執行期間完成取消操做,咱們本身寫代碼判斷 cancellationToken.IsCancellationRequested 屬性是否爲 true(該屬性在調用 source.Cancel() 後或者 source.CancelAfter() 方法到達指定時間後爲 true),若是爲 true 結束執行便可。
2) 這裏所說的「傳達取消請求」的意思是,每一個 Task 在執行以前都會檢查 cancellationToken.IsCancellationRequested 屬性是否爲 true,若是爲 true 則不執行 Task,並將設置 Status、IsCompleted、IsCanceled 等。
3) 因此,在 Task 的源碼中有這樣一段代碼

if (cancellationToken.IsCancellationRequested)
{
    // Fast path for an already-canceled cancellationToken
    this.InternalCancel(false);
}

3. 乘熱打鐵,咱們再來看看多個 CancellationTokenSource 取消異步任務,以及註冊取消後的回調委託方法,繼續以(模擬去買鹽)爲例:

/// <summary>
/// 作飯(多個消息傳達買鹽任務取消)
/// </summary>
public void CookDinner_MultiCancelBuySalt()
{
    Console.WriteLine("老婆開始作飯,線程Id爲:{0}", GetThreadId());
    Console.WriteLine("哎呀,沒鹽了");
    CancellationTokenSource source1 = new CancellationTokenSource();    //由於存在而取消
    CancellationTokenSource source2 = new CancellationTokenSource();    //由於放棄而取消
 
    CancellationTokenSource source = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);
 
    //註冊取消時的回調委託
    source1.Token.Register(() =>
    {
        Console.WriteLine("這是由於{0}因此取消,線程Id爲:{1}", "家裏還有鹽", GetThreadId());
    });
 
    source2.Token.Register((state) =>
    {
        Console.WriteLine("這是由於{0}因此取消,線程Id爲:{1}", state, GetThreadId());
    }, "不作了出去吃");
 
    source.Token.Register((state) =>
    {
        Console.WriteLine("這是由於{0}因此取消,線程Id爲:{1}", state, GetThreadId());
    }, "沒理由");
 
    //這裏必須傳遞 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 對象
    Task<string> task = CommandBuySalt_MultiCancelBuySalt(source.Token);
 
    Console.WriteLine("等等,好像不用買了,線程Id爲:{0}", GetThreadId());
    Thread.Sleep(100);
 
    string[] results = new string[] { "家裏的鹽", "不作了出去吃", "沒理由" };
    Random r = new Random();
    switch (r.Next(1, 4))
    {
        case 1:
            source1.Cancel();           //傳達取消請求(家裏有鹽)
            //source1.CancelAfter(3000);  //3s後才調用取消的回調方法
            Console.WriteLine("既然有鹽我就繼續炒菜【{0}】,線程Id爲:{1}", results[0], GetThreadId());
            break;
        case 2:
            source2.Cancel();           //傳達取消請求(不作了出去吃)
            //source2.CancelAfter(3000);  //3s後才調用取消的回調方法
            Console.WriteLine("咱們出去吃不用買啦【{0}】,線程Id爲:{1}", results[1], GetThreadId());
            break;
        case 3:
            source.Cancel();            //傳達取消請求(沒理由)
            //source.CancelAfter(3000);   //3s後才調用取消的回調方法
            Console.WriteLine("沒理由就是不用買啦【{0}】,線程Id爲:{1}", results[2], GetThreadId());
            break;
    }
 
    Console.WriteLine("最終的任務狀態是:{0},已完成:{1},已取消:{2},已失敗:{3}",
        task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
}
 
/// <summary>
/// 通知我去買鹽(又告訴我不用買了,各類理由)
/// </summary>
public async Task<string> CommandBuySalt_MultiCancelBuySalt(CancellationToken token)
{
    Console.WriteLine("這時我準備去買鹽了,線程Id爲:{0}", GetThreadId());
 
    //已開始執行的任務不能被取消
    string result = await Task.Run(() =>
    {
        Console.WriteLine("屁顛屁顛的去買鹽,線程Id爲:{0}", GetThreadId());
        Thread.Sleep(1000);
    }, token).ContinueWith((t) =>  //若沒有取消就繼續執行
    {
        Console.WriteLine("鹽已經買好了,線程Id爲:{0}", GetThreadId());
        Thread.Sleep(1000);
        return "鹽買回來了,順便我還買了一包煙";
    }, token);
 
    Console.WriteLine("{0},線程Id爲:{1}", result, GetThreadId());
 
    return result;
}

運行以上代碼:

1)   當調用 source.Cancel() 方法後,會當即取消並調用 token 註冊的回調方法;而調用 existSource.CancelAfter() 方法則會等到達指定的毫秒數後纔會取消。
2)   注意:傳遞給異步方法的 token 對象,必須是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 對象,不然取消將無效。
3)   回調的委託方法始終只有兩個,一個是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 對象的註冊委託,另外一個是調用 Cancel()/CancelAfter() 方法的 Token 對象的註冊委託。
4)   若是以上代碼調用的是 CancelAfter(3000) 方法,運行結果以下:

原文連接: http://www.javashuo.com/article/p-qjheqqch-ns.html

前言
C#異步編程有幾種實現方式,異步方法就是其中的一種。異步方法是 C#5.0 纔有的新特性,主要採用 async、await 關鍵字聲明爲異步方法,完成對方法的異步調用。C#5.0 對應的 VS 版本是 VS2012,對應的 .NET Framework 版本是 v4.5,因此須要在此基礎上才支持。(不然可能報:找不到「async」修飾符所需的全部類型。目標框架版本是否不正確,或者缺乏對程序集的引用?)

什麼是異步方法
1. 異步方法,是指在執行當前方法的同時,能夠異步的去調用其餘方法(異步方法),而且不會阻塞當前方法的線程。
2. 使用了 async 修飾符的方法稱爲異步方法,一般配合 await 運算符和 Task 異步任務一塊兒使用。
1) 若是方法使用了 async 修飾符,則方法中須要包含一個以上 await 運算符,不然將以同步執行。
2) 反之,若是方法中包含一個以上 await 運算符,則必須聲明爲一個異步方法,即便用 async 修飾符。

3. Task 分爲兩種:
1) Task,表示能夠執行一個異步操做,聲明以下:
public class Task : IAsyncResult, IDisposable { }
2) Task<TResult>,表示能夠執行帶有返回值的異步操做,聲明以下:
public class Task<TResult> : Task { }

4. 異步方法的返回類型必須爲 void、Task、Task<TResult> 中的其中一種。
1) void,表示無返回值,不關心異步方法執行後的結果,通常用於僅僅執行某一項任務,可是不關心結果的場景。
2) Task,表示異步方法將返回一個 Task 對象,該對象一般用於判斷異步任務是否已經完成,可使用 taskObj.Wait() 方法等待,或者 taskObj.IsCompleted 判斷。
3) Task<TResult>,表示異步方法將返回一個 Task<TResult> 對象,該對象的 Result 屬性則是異步方法的執行結果,調用該屬性時將阻塞當前線程(異步方法未執行完成時)。

概括一下:void 不關心結果;Task 只關心是否執行完成;Task<TResult> 不止關心是否執行完成,還要獲取執行結果。

下面經過幾個生活中比較形象的例子來理解異步方法的使用
1. 模擬扔垃圾(不關心結果,返回 void 類型)

  1.  
    /// <summary>
  2.  
    /// 扔垃圾
  3.  
    /// </summary>
  4.  
    public void DropLitter()
  5.  
    {
  6.  
        Console.WriteLine( "老婆開始打掃房間,線程Id爲:{0}", GetThreadId());
  7.  
        Console.WriteLine( "垃圾滿了,快去扔垃圾");
  8.  
        CommandDropLitter();
  9.  
        Console.WriteLine( "無論他繼續打掃,線程Id爲:{0}", GetThreadId());
  10.  
        Thread.Sleep( 100);
  11.  
        Console.WriteLine( "老婆把房間打掃好了,線程Id爲:{0}", GetThreadId());
  12.  
    }
  13.  
     
  14.  
    /// <summary>
  15.  
    /// 通知我去扔垃圾
  16.  
    /// </summary>
  17.  
    public async void CommandDropLitter()
  18.  
    {
  19.  
        Console.WriteLine( "這時我準備去扔垃圾,線程Id爲:{0}", GetThreadId());
  20.  
         await Task.Run(() =>
  21.  
        {
  22.  
            Console.WriteLine( "屁顛屁顛的去扔垃圾,線程Id爲:{0}", GetThreadId());
  23.  
            Thread.Sleep( 1000);
  24.  
        });
  25.  
        Console.WriteLine( "垃圾扔了還有啥吩咐,線程Id爲:{0}", GetThreadId());
  26.  
    }

運行以上代碼:

以上代碼在 CommandDropLitter() 方法上加了 async 修飾符,而且使用 await 運算符開啓了一個新的 Task 去執行另外一個任務。注意:當前線程遇到 await 時,則馬上跳回調用方法繼續往下執行。而 Task 執行完成以後將執行 await 以後的代碼,而且與 await 以前的線程不是同一個。

2.模擬打開電源開關(關心是否執行完成,返回 Task 類型)

  1.  
    /// <summary>
  2.  
    /// 打開電源開關
  3.  
    /// </summary>
  4.  
    public void OpenMainsSwitch()
  5.  
    {
  6.  
        Console.WriteLine( "我和老婆正在看電視,線程Id爲:{0}", GetThreadId());
  7.  
        Console.WriteLine( "忽然停電了,快去看下是否是跳閘了");
  8.  
        Task task = CommandOpenMainsSwitch();
  9.  
        Console.WriteLine( "沒電了先玩會兒手機吧,線程Id爲:{0}", GetThreadId());
  10.  
        Thread.Sleep( 100);
  11.  
        Console.WriteLine( "手機也沒電了只等電源打開,線程Id爲:{0}", GetThreadId());
  12.  
     
  13.  
         //task.Wait();    //因此這裏將被阻塞,直到任務完成
  14.  
         //或者
  15.  
         while (!task.IsCompleted) { Thread.Sleep( 100); }
  16.  
     
  17.  
        Console.WriteLine( "又有電了咱們繼續看電視,線程Id爲:{0}", GetThreadId());
  18.  
    }
  19.  
     
  20.  
    /// <summary>
  21.  
    /// 通知我去打開電源開關
  22.  
    /// </summary>
  23.  
    public async Task CommandOpenMainsSwitch()
  24.  
    {
  25.  
        Console.WriteLine( "這時我準備去打開電源開關,線程Id爲:{0}", GetThreadId());
  26.  
         await Task.Run(() =>
  27.  
        {
  28.  
            Console.WriteLine( "屁顛屁顛的去打開電源開關,線程Id爲:{0}", GetThreadId());
  29.  
            Thread.Sleep( 1000);
  30.  
        });
  31.  
     
  32.  
        Console.WriteLine( "電源開關打開了,線程Id爲:{0}", GetThreadId());
  33.  
    }

運行以上代碼:

1) 可見,調用 Wait() 方法後,當前線程被阻塞了,直到 Task 執行完成後,當前線程才繼續執行。
2) 注意:因爲 CommandOpenMainsSwitch() 是一個異步方法,雖然返回類型爲 Task 類型,可是在咱們代碼中並無寫(也不能寫) return task 語句,這是爲何呢?多是這種返回類型比較特殊,或者編譯器自動幫咱們完成了吧!就算寫也只能寫 return 語句,後面不能跟對象表達式。

3. 模擬去買鹽(不止關心是否執行完成,還要獲取執行結果。返回 Task<TResult> 類型)

  1.  
    /// <summary>
  2.  
    /// 作飯
  3.  
    /// </summary>
  4.  
    public void CookDinner()
  5.  
    {
  6.  
        Console.WriteLine( "老婆開始作飯,線程Id爲:{0}", GetThreadId());
  7.  
        Console.WriteLine( "哎呀,沒鹽了");
  8.  
        Task< string> task = CommandBuySalt();
  9.  
        Console.WriteLine( "無論他繼續炒菜,線程Id爲:{0}", GetThreadId());
  10.  
        Thread.Sleep( 100);
  11.  
         string result = task.Result;     //必需要用鹽了,等我把鹽回來(中止炒菜(阻塞線程))
  12.  
        Console.WriteLine( "用了鹽炒的菜就是好吃【{0}】,線程Id爲:{1}", result, GetThreadId());
  13.  
        Console.WriteLine( "老婆把飯作好了,線程Id爲:{0}", GetThreadId());
  14.  
    }
  15.  
     
  16.  
    /// <summary>
  17.  
    /// 通知我去買鹽
  18.  
    /// </summary>
  19.  
    public async Task<string> CommandBuySalt()
  20.  
    {
  21.  
        Console.WriteLine( "這時我準備去買鹽了,線程Id爲:{0}", GetThreadId());
  22.  
     
  23.  
         string result = await Task.Run(() =>
  24.  
        {
  25.  
            Console.WriteLine( "屁顛屁顛的去買鹽,線程Id爲:{0}", GetThreadId());
  26.  
            Thread.Sleep( 1000);
  27.  
             return "鹽買回來了,順便我還買了一包煙";
  28.  
     
  29.  
        });
  30.  
     
  31.  
        Console.WriteLine( "{0},線程Id爲:{1}", result, GetThreadId());
  32.  
     
  33.  
         return result;
  34.  
    }

運行以上代碼:

1) 以上代碼 task.Result 會阻塞當前線程,與 task.Wait() 相似。
2) 注意:與前面返回類型爲 Task 的 CommandOpenMainsSwitch() 方法同樣,雖然 CommandBuySalt() 方法返回類型爲 Task<string>,可是咱們的返回語句是 return 字符串。

其餘示例
1. 在前面(模擬去買鹽)的示例中,異步方法中只開啓了一個 Task,若是開啓多個 Task 又是什麼狀況,看代碼:

  1.  
    public void AsyncTest()
  2.  
    {
  3.  
        Console.WriteLine( "AsyncTest() 方法開始執行,線程Id爲:{0}", GetThreadId());
  4.  
        Task task = Test1();
  5.  
        Console.WriteLine( "AsyncTest() 方法繼續執行,線程Id爲:{0}", GetThreadId());
  6.  
        task.Wait();
  7.  
        Console.WriteLine( "AsyncTest() 方法結束執行,線程Id爲:{0}", GetThreadId());
  8.  
    }
  9.  
     
  10.  
    public async Task Test1()
  11.  
    {
  12.  
        Console.WriteLine( "Test1() 方法開始執行,線程Id爲:{0}", GetThreadId());
  13.  
         await Task.Factory.StartNew((state) =>
  14.  
        {
  15.  
            Console.WriteLine( "Test1() 方法中的 {0} 開始執行,線程Id爲:{1}", state, GetThreadId());
  16.  
            Thread.Sleep( 1000);
  17.  
            Console.WriteLine( "Test1() 方法中的 {0} 結束執行,線程Id爲:{1}", state, GetThreadId());
  18.  
        }, "task1");
  19.  
     
  20.  
         await Task.Factory.StartNew((state) =>
  21.  
        {
  22.  
            Console.WriteLine( "Test1() 方法中的 {0} 開始執行,線程Id爲:{1}", state, GetThreadId());
  23.  
            Thread.Sleep( 3000);
  24.  
            Console.WriteLine( "Test1() 方法中的 {0} 結束執行,線程Id爲:{1}", state, GetThreadId());
  25.  
        }, "task2");
  26.  
     
  27.  
        Console.WriteLine( "Test1() 方法結束執行,線程Id爲:{0}", GetThreadId());
  28.  
    }

運行以上代碼:

當異步方法中有多個 await 時,會依次執行全部的 Task,只有當全部 Task 執行完成後才表示異步方法執行完成,當前線程才得以執行。

2. 一樣之前面(模擬去買鹽)的示例,若是發現其實家裏還有鹽,這是就要告訴我不用買了(取消異步操做),怎麼實現?這就要藉助 System.Threading.CancellationTokenSource 和 System.Threading.Tasks.CancellationToken 對象來完成。

  1.  
    /// <summary>
  2.  
    /// 作飯(買鹽任務取消)
  3.  
    /// </summary>
  4.  
    public void CookDinner_CancelBuySalt()
  5.  
    {
  6.  
        Console.WriteLine( "老婆開始作飯,線程Id爲:{0}", GetThreadId());
  7.  
        Console.WriteLine( "哎呀,沒鹽了");
  8.  
        CancellationTokenSource source = new CancellationTokenSource();
  9.  
        Task< string> task = CommandBuySalt_CancelBuySalt(source.Token);
  10.  
        Console.WriteLine( "無論他繼續炒菜,線程Id爲:{0}", GetThreadId());
  11.  
        Thread.Sleep( 100);
  12.  
     
  13.  
         string result = "家裏的鹽";
  14.  
         if (! string.IsNullOrEmpty(result))
  15.  
        {
  16.  
            source.Cancel();     //傳達取消請求
  17.  
            Console.WriteLine( "家裏還有鹽不用買啦,線程Id爲:{0}", GetThreadId());
  18.  
        }
  19.  
         else
  20.  
        {
  21.  
             //若是已取消就不能再得到結果了(不然將拋出 System.Threading.Tasks.TaskCanceledException 異常)
  22.  
             //你都叫我不要買了,我拿什麼給你?
  23.  
            result = task.Result;
  24.  
        }
  25.  
     
  26.  
        Console.WriteLine( "既然有鹽我就繼續炒菜【{0}】,線程Id爲:{1}", result, GetThreadId());
  27.  
        Console.WriteLine( "老婆把飯作好了,線程Id爲:{0}", GetThreadId());
  28.  
        Console.WriteLine( "最終的任務狀態是:{0},已完成:{1},已取消:{2},已失敗:{3}",
  29.  
            task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
  30.  
    }
  31.  
     
  32.  
    /// <summary>
  33.  
    /// 通知我去買鹽(又告訴我不用買了)
  34.  
    /// </summary>
  35.  
    public async Task<string> CommandBuySalt_CancelBuySalt(CancellationToken token)
  36.  
    {
  37.  
        Console.WriteLine( "這時我準備去買鹽了,線程Id爲:{0}", GetThreadId());
  38.  
     
  39.  
         //已開始執行的任務不能被取消
  40.  
         string result = await Task.Run(() =>
  41.  
        {
  42.  
            Console.WriteLine( "屁顛屁顛的去買鹽,線程Id爲:{0}", GetThreadId());
  43.  
            Thread.Sleep( 1000);
  44.  
        }, token).ContinueWith((t) =>   //若沒有取消就繼續執行
  45.  
        {
  46.  
            Console.WriteLine( "鹽已經買好了,線程Id爲:{0}", GetThreadId());
  47.  
            Thread.Sleep( 1000);
  48.  
     
  49.  
             return "鹽買回來了,順便我還買了一包煙";
  50.  
        }, token);
  51.  
     
  52.  
        Console.WriteLine( "{0},線程Id爲:{1}", result, GetThreadId());
  53.  
     
  54.  
         return result;
  55.  
    }

運行以上代碼:

1) 剛開始我覺得調用 source.Cancel() 方法後會當即取消 Task 的執行,仔細一想也不太可能。若是須要在 Task 執行前或者執行期間完成取消操做,咱們本身寫代碼判斷 cancellationToken.IsCancellationRequested 屬性是否爲 true(該屬性在調用 source.Cancel() 後或者 source.CancelAfter() 方法到達指定時間後爲 true),若是爲 true 結束執行便可。
2) 這裏所說的「傳達取消請求」的意思是,每一個 Task 在執行以前都會檢查 cancellationToken.IsCancellationRequested 屬性是否爲 true,若是爲 true 則不執行 Task,並將設置 Status、IsCompleted、IsCanceled 等。
3) 因此,在 Task 的源碼中有這樣一段代碼

  1.  
    if (cancellationToken.IsCancellationRequested)
  2.  
    {
  3.  
        // Fast path for an already-canceled cancellationToken
  4.  
        this.InternalCancel( false);
  5.  
    }

3. 乘熱打鐵,咱們再來看看多個 CancellationTokenSource 取消異步任務,以及註冊取消後的回調委託方法,繼續以(模擬去買鹽)爲例:

  1.  
    /// <summary>
  2.  
    /// 作飯(多個消息傳達買鹽任務取消)
  3.  
    /// </summary>
  4.  
    public void CookDinner_MultiCancelBuySalt()
  5.  
    {
  6.  
        Console.WriteLine( "老婆開始作飯,線程Id爲:{0}", GetThreadId());
  7.  
        Console.WriteLine( "哎呀,沒鹽了");
  8.  
        CancellationTokenSource source1 = new CancellationTokenSource();     //由於存在而取消
  9.  
        CancellationTokenSource source2 = new CancellationTokenSource();     //由於放棄而取消
  10.  
     
  11.  
        CancellationTokenSource source = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);
  12.  
     
  13.  
         //註冊取消時的回調委託
  14.  
        source1.Token.Register(() =>
  15.  
        {
  16.  
            Console.WriteLine( "這是由於{0}因此取消,線程Id爲:{1}", "家裏還有鹽", GetThreadId());
  17.  
        });
  18.  
     
  19.  
        source2.Token.Register((state) =>
  20.  
        {
  21.  
            Console.WriteLine( "這是由於{0}因此取消,線程Id爲:{1}", state, GetThreadId());
  22.  
        }, "不作了出去吃");
  23.  
     
  24.  
        source.Token.Register((state) =>
  25.  
        {
  26.  
            Console.WriteLine( "這是由於{0}因此取消,線程Id爲:{1}", state, GetThreadId());
  27.  
        }, "沒理由");
  28.  
     
  29.  
         //這裏必須傳遞 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 對象
  30.  
        Task< string> task = CommandBuySalt_MultiCancelBuySalt(source.Token);
  31.  
     
  32.  
        Console.WriteLine( "等等,好像不用買了,線程Id爲:{0}", GetThreadId());
  33.  
        Thread.Sleep( 100);
  34.  
     
  35.  
         string[] results = new string[] { "家裏的鹽", "不作了出去吃", "沒理由" };
  36.  
        Random r = new Random();
  37.  
         switch (r.Next( 1, 4))
  38.  
        {
  39.  
             case 1:
  40.  
                source1.Cancel();           //傳達取消請求(家裏有鹽)
  41.  
                 //source1.CancelAfter(3000);  //3s後才調用取消的回調方法
  42.  
                Console.WriteLine( "既然有鹽我就繼續炒菜【{0}】,線程Id爲:{1}", results[ 0], GetThreadId());
  43.  
                 break;
  44.  
             case 2:
  45.  
                source2.Cancel();           //傳達取消請求(不作了出去吃)
  46.  
                 //source2.CancelAfter(3000);  //3s後才調用取消的回調方法
  47.  
                Console.WriteLine( "咱們出去吃不用買啦【{0}】,線程Id爲:{1}", results[ 1], GetThreadId());
  48.  
                 break;
  49.  
             case 3:
  50.  
                source.Cancel();             //傳達取消請求(沒理由)
  51.  
                 //source.CancelAfter(3000);   //3s後才調用取消的回調方法
  52.  
                Console.WriteLine( "沒理由就是不用買啦【{0}】,線程Id爲:{1}", results[ 2], GetThreadId());
  53.  
                 break;
  54.  
        }
  55.  
     
  56.  
        Console.WriteLine( "最終的任務狀態是:{0},已完成:{1},已取消:{2},已失敗:{3}",
  57.  
            task.Status, task.IsCompleted, task.IsCanceled, task.IsFaulted);
  58.  
    }
  59.  
     
  60.  
    /// <summary>
  61.  
    /// 通知我去買鹽(又告訴我不用買了,各類理由)
  62.  
    /// </summary>
  63.  
    public async Task<string> CommandBuySalt_MultiCancelBuySalt(CancellationToken token)
  64.  
    {
  65.  
        Console.WriteLine( "這時我準備去買鹽了,線程Id爲:{0}", GetThreadId());
  66.  
     
  67.  
         //已開始執行的任務不能被取消
  68.  
         string result = await Task.Run(() =>
  69.  
        {
  70.  
            Console.WriteLine( "屁顛屁顛的去買鹽,線程Id爲:{0}", GetThreadId());
  71.  
            Thread.Sleep( 1000);
  72.  
        }, token).ContinueWith((t) =>   //若沒有取消就繼續執行
  73.  
        {
  74.  
            Console.WriteLine( "鹽已經買好了,線程Id爲:{0}", GetThreadId());
  75.  
            Thread.Sleep( 1000);
  76.  
             return "鹽買回來了,順便我還買了一包煙";
  77.  
        }, token);
  78.  
     
  79.  
        Console.WriteLine( "{0},線程Id爲:{1}", result, GetThreadId());
  80.  
     
  81.  
         return result;
  82.  
    }

運行以上代碼:

1)   當調用 source.Cancel() 方法後,會當即取消並調用 token 註冊的回調方法;而調用 existSource.CancelAfter() 方法則會等到達指定的毫秒數後纔會取消。2)   注意:傳遞給異步方法的 token 對象,必須是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 對象,不然取消將無效。3)   回調的委託方法始終只有兩個,一個是 CancellationTokenSource.CreateLinkedTokenSource() 方法返回的 Token 對象的註冊委託,另外一個是調用 Cancel()/CancelAfter() 方法的 Token 對象的註冊委託。4)   若是以上代碼調用的是 CancelAfter(3000) 方法,運行結果以下:

相關文章
相關標籤/搜索