14.并發(fā)與異步 - 3.C#5.0的異步函數(shù) -《果殼中的c#》

14.5.2 編寫異步函數(shù)

private static readonly Stopwatch Watch = new Stopwatch();
        static void Main(string[] args)
        {
           Go();
            Console.Read();
        }

        private static async Task Go()
        {
            await PrintAnswerToLife();
            Console.WriteLine("Done");
        }
       
        private static async Task PrintAnswerToLife()   // We can return Task instead of void
        {
            await Task.Delay(5000);
            int answer = 21 * 2;
            Console.WriteLine(answer);
        }

編譯器會擴展異步函數(shù),它會將任務(wù)返回給使用TaskCompletionSource的代碼悟狱,用于創(chuàng)建任務(wù)静浴,然后再發(fā)送信號或異常終止。
除了這些細微區(qū)別挤渐,可以將PrintAnswerToLife擴展為下面的等價功能:

        private static Task PrintAnswerToLife()  
        {
            var tcs = new TaskCompletionSource<object>();
            var awaiter = Task.Delay(5000).GetAwaiter();
            awaiter.OnCompleted(() =>
            {
                try
                {
                    awaiter.GetResult();
                    int answer = 21 * 2;
                    Console.WriteLine(answer + " 耗時:" + Watch.ElapsedMilliseconds + "ms");
                    tcs.SetResult(null);
                }
                catch (Exception ex)
                {
                    tcs.SetException(ex);
                }
            });
            return tcs.Task;
        }

因此苹享,當(dāng)一個返回任務(wù)的異步方法結(jié)束時霍衫,執(zhí)行過程會返回等待它的程序(通過一個延續(xù))窿春。

1.返回 Task<TResult>

async Task<int> GetAnswerToLife()
{
    await Task.Delay (5000);
    int answer = 21 * 2;
    return answer; //返回類型是Task<int>  所以返回int
}

在內(nèi)部,這段代碼向TaskCompletionSource發(fā)送一個值丈氓,而非null软免。

        void Main()
        {
            Go();
        }
        
        async Task Go()
        {
            await PrintAnswerToLife();
            Console.WriteLine ("Done");
        }
        
        async Task PrintAnswerToLife()
        {
            int answer = await GetAnswerToLife();
            Console.WriteLine (answer);
        }
        
        async Task<int> GetAnswerToLife()
        {
            await Task.Delay (5000);
            int answer = 21 * 2;
            return answer;
        }

編譯能夠為異步函數(shù)創(chuàng)建任務(wù)宫纬,意味我們只需在 I/O 綁定代碼底層方法中顯式創(chuàng)建一個'TaskCompletionSource'實例。(CPU 綁定代碼可以使用 Task.Run創(chuàng)建任務(wù))

2.異步調(diào)用圖的執(zhí)行

為了確切理解執(zhí)行過程膏萧,最好將代碼重新排列:

        static async Task Go()
        {
            var task = PrintAnswerToLife();
            await task;
            Console.WriteLine("Done");
        }

        static async Task PrintAnswerToLife()  
        {
            var task = GetAnswerToLife();
            int answer = await task;
            Console.WriteLine(answer);
        }

        static async Task<int> GetAnswerToLife()
        {
            var task = Task.Delay(5000);
            await task;
            int answer = 21 * 2;
            return answer;
        }

await 會使執(zhí)行過程返回它所等待的PrintAnswerToLife漓骚,然后再返回Go蝌衔,它同樣會等待并返回調(diào)用者。所有這些方法調(diào)用都在調(diào)用Go的線程上以同步方式執(zhí)行蝌蹂;這是執(zhí)行過程的主要同步階段噩斟。

整個執(zhí)行流程在每一個異步調(diào)用后都會等待。這樣就可以在調(diào)用圖中形成一個無并發(fā)或重疊的串行流孤个。每一個await表達式都會執(zhí)行中創(chuàng)建一個“缺口”剃允,之后程序都可以在原處恢復(fù)執(zhí)行。

3.并行性

調(diào)用一個異步方法齐鲤,但是等待它斥废,就可以使代碼并行執(zhí)行。前面例子佳遂,有一個按鈕添加一個像下面這樣的事件處理器Go:

_buttion.Click += (sender, args) => Go();

盡管Go是一個異步方法营袜,但是我們并沒有等待它,事實上它正是利用并發(fā)性來實現(xiàn)快速響應(yīng)UI:

我們可以使用相同的法則以并行方式執(zhí)行兩個異步操作:

var task1 = PrintAnswerToLife();
var task2 = PrintAnswerToLife();
await task1;
await task2;

以這種方式創(chuàng)建的并發(fā)性可以支持UI線程或非UI線程上執(zhí)行的操作丑罪,但是它們實現(xiàn)方式有所區(qū)別荚板。這兩種情況都可以在底層操作上(如Task.DelayTask.Run生成的代碼)實現(xiàn)真正的并發(fā)性。

在調(diào)用堆中吩屹,只有操作不通過同步上下文創(chuàng)建跪另,在這之上的方法才可以實現(xiàn)真正的并發(fā)性;否則煤搜,它們就是前面介紹的偽并發(fā)性和簡化的線程安全性免绿,其中我們唯一能夠優(yōu)先使用的是await語句。

例如擦盾,它允許我們定義一個共享域_x嘲驾,然后不需要使用鎖就可以在增加它的值:

        private static async Task PrintAnswerToLife()   
        {
            _x++;
            await Task.Delay(5000);
            return 21 * 2;
        }

(但是,這里不能假定_x在await前后均保持相同的值迹卢。)

14.5.3 異步Lambda表達式

就像普通的命名(named)方法可以采用異步方式執(zhí)行一樣:

async Task NamedMethod()
{
    await Task.Delay (1000);
    Console.WriteLine ("Foo");
}

只要添加async關(guān)鍵字辽故,未命名(unnamed)方法也可以采用異步:

async void Main()
{
    Func<Task> unnamed = async () =>
    {
        await Task.Delay (1000);
        Console.WriteLine ("Foo1");
    };

    // We can call the two in the same way:
    await NamedMethod();
    await unnamed();
}

異步lambda表達式可用于附加事件處理器:

myButton.Click += async (sender, args) =>
{
    await Task.Delay (1000);
    myButton.Content = "Done";
};

下面代碼更簡潔:

myButton.Click +=ButtonHandler;

async void ButtonHandler(object sender, EventArgs args)
{
    await Task.Delay (1000);
    myButton.Content = "Done";
}

異步lambda表達式也可以返回Task<Result>

Func<Task<int>> unnamed = async () =>
{
    await Task.Delay (1000);
    return 123;
};

int answer = await unnamed();

14.5.4 WinRT異步方法

WinRT中,

Task等價IAsyncAction腐碱,
Task<TResult>等價IAsyncOperation<TResult>誊垢。

兩個類都通過System.Runtime.WindowsRuntime.dll程序集的AsTask擴展方法轉(zhuǎn)換為TaskTask<TResult>。這個程序集也定義了一個GetAwaiter方法症见,它可以操作IAsyncActionIAsyncOperation<TResult>喂走,他們可以直接執(zhí)行等待操作。

Task<StorageFile> file = 
KnowFolders.DocumentsLibrary.CreateFileAsync("test.txt").AsTask();

或者:

StorageFile file = 
await KnowFolders.DocumentsLibrary.CreateFileAsync("test.txt");

14.5.5 異步與同步上下文

1.異常提交

2.OpertionStarted 和 OperationCompleted

14.5.6 優(yōu)化

1.同步完成

異步方法可能會在等待之前返回谋作,假設(shè)有下面這樣方法芋肠,它會緩存下載的網(wǎng)頁:

static Dictionary<string,string> _cache = new Dictionary<string,string>();

async Task<string> GetWebPageAsync (string uri)
{
    string html;
    if (_cache.TryGetValue (uri, out html)) return html;
    return _cache [uri] = await new WebClient().DownloadStringTaskAsync (uri);
}

假設(shè)某個URI已經(jīng)存在于緩存之中,那么執(zhí)行過程會在等待發(fā)生之前返回調(diào)用者遵蚜,同時這個方法會返回一個已發(fā)送信號的任務(wù)业栅,這稱為同步完成秒咐。

如果等待一個同步完成任務(wù),那么執(zhí)行過程不會返回調(diào)用者并通過一個延續(xù)彈回——相反碘裕,它會馬上進入下一條語句携取。編譯器會通過檢查等待著的IsCompleted屬性來實現(xiàn)這種優(yōu)化;換言之帮孔,無論何時執(zhí)行等待:

Console.WriteLine(await GetWebPageAsync ("http://oreilly.com"));

在同步完成時雷滋,編譯器會生成中止延續(xù)的代碼:

var awaiter = GetWebPageAsync().GetAwaiter();
    if (awaiter.IsCompleted)
        Console.WriteLine(awaiter.GetResult());
    else
        awaiter.OnCompleted(()=>Console.WriteLine(awaiter.GetResult()));

編寫從不等待的異步方法是允許的,但是編譯器會發(fā)出警告:

async Task<string> Foo() {return "abc";}

在重寫虛方法/抽象方法時文兢,如果不需要實現(xiàn)異步處理晤斩,那么很適合使用這種方法。

實現(xiàn)相同結(jié)果的另一種方法是使用Task.FromResult姆坚,它會返回一個已發(fā)送信號的任務(wù)澳泵。

    Task<string> Too()
    {
        return Task.FromResult("abc");
    }

如果從UI線程調(diào)用,GetWebPageAsync方法本身就具有線程安全性兼呵,在成功執(zhí)行后多次調(diào)用這個方法(初始化多個并發(fā)下載)兔辅,而且不用鎖來保證緩存。

但是击喂,多次處理同個URI维苔,會生成多個冗余下載,最終更新同一個緩存記錄(最后個覆蓋前面)懂昂。如果沒有錯介时,那更高效的方式是讓同一個URI的后續(xù)調(diào)用(異步)等待正在處理的請求。

還有一個簡單方法(不需要鎖或信號結(jié)構(gòu)):

創(chuàng)建一個“未來”緩存(Task<string>)凌彬,代替字符串緩存:

static Dictionary<string,Task<string>> _cache = 
   new Dictionary<string,Task<string>>();  

Task<string> GetWebPageAsync (string uri)
{
    Task<string> downloadTask;
    if (_cache.TryGetValue (uri, out downloadTask)) return downloadTask;
    return _cache [uri] = new WebClient().DownloadStringTaskAsync (uri);
}

這里沒有使用await沸柔,直接返回獲得的任務(wù)。

如果重復(fù)調(diào)用GetWebPageAsync處理同一個URI铲敛,可以保證能獲得同一個Task<string>對象褐澎。(這樣做另一個好處,降低GC負載)

2.避免過度回彈

ConfigureAwait的作用:使當(dāng)前async方法的await后續(xù)操作不需要恢復(fù)到主線程(不需要保存線程上下文)原探。

對于循環(huán)中多次調(diào)用的方法乱凿,通過調(diào)用ConfigureAwait顽素,可以避免重復(fù)回彈UI消息循環(huán)帶來的開銷咽弦。

void Main()
{
    A();
}

async void A()
{
    await B(); 
}

async Task B()
{
    for (int i = 0; i < 1000; i++)
        await C().ConfigureAwait (false);
}

async Task C() { /*...*/ }

B方法和C方法撤銷UI使用的簡單線程安全模式,代碼運行在UI線程上胁出,而只能在await語句中優(yōu)先占用型型。然而,A方法不受影響全蝶,它在啟動之后就一直停留在UI線程闹蒜。

14.6 異步模式

14.6.1 取消

通常要能夠在并發(fā)操作啟動后寺枉,取消這個操作(用戶請求)。實現(xiàn)這個操作的簡單方式是使用取消令牌绷落,編寫一個封裝類:

class CancellationToken
{
    public bool IsCancellationRequested { get; private set; }
    public void Cancel() { IsCancellationRequested = true; }
    public void ThrowIfCancellationRequested()
    {
        if (IsCancellationRequested) throw new OperationCanceledException();
    }
}

當(dāng)調(diào)用者想取消操作時姥闪,它會調(diào)用傳遞給Foo的取消令牌上的Cancel。因此出現(xiàn)OperationCanceledException異常砌烁。

例:

async void Main()
{
    var token = new CancellationToken();
    Task.Delay (5000).ContinueWith (ant => token.Cancel());   // Tell it to cancel in two seconds.
    await Foo (token);
}

// This is a simplified version of the CancellationToken type in System.Threading:
class CancellationToken
{
    public bool IsCancellationRequested { get; private set; }
    public void Cancel() { IsCancellationRequested = true; }
    public void ThrowIfCancellationRequested()
    {
        if (IsCancellationRequested) throw new OperationCanceledException();
    }
}

async Task Foo (CancellationToken cancellationToken)
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine (i);
        await Task.Delay (1000);
        cancellationToken.ThrowIfCancellationRequested();
    }
}
自定義CancellationToken類

CLR提供一個CancellationToken類型筐喳,然而它沒有Cancel()方法;

但是這個方法提供另一個類型CancellationTokenSource函喉。這種分離具有一定安全性:只能訪問CancellationToken對象的方法可以檢查取消操作避归,但不能初始化取消操作。

CancellationTokenSource有一個Token屬性管呵,可以返回一個CancellationToken梳毙。

 var cancelSource  = new CancellationTokenSource();
 
Task.Delay(5000).ContinueWith(ant => cancelSource.Cancel());
await Foo (cancelSource.Token);

在CLR中,大多數(shù)異步方法提供了取消令牌捐下,包括Delay账锹。

   public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken);

Task.Delay(millisecondsDelay,CancellationToken)

我們不需要再調(diào)用ThrowIfCancellationRequested蔑担,因為Task.Delay已經(jīng)包含這個操作牌废。

同步方法也支持取消操作(如Task.Wait方法)。這種情況啤握,取消指令必須以異步方式執(zhí)行(例如鸟缕,在另一個任務(wù)中執(zhí)行)。

例如:

var cancelSource  = new CancellationTokenSource(5000);
Task.Delay(5000).ContinueWith(ant => cancelSource.Cancel());  
...

Framework 4.5開始排抬,創(chuàng)建CancellationTokenSource可以指定一個時間間隔懂从,表示一定時間段后初始化取消操作。

無論同步或者異步,最好指定一個超時時間:

    var cancelSource = new CancellationTokenSource (5000);
    try
    {           
        await Foo (cancelSource.Token);
    }
    catch (OperationCanceledException ex)
    {
        Console.WriteLine ("Cancelled");
    }  

CancellationToken結(jié)構(gòu)提供一個Register方法蹲蒲,可以用于注冊一個回調(diào)代理番甩,然后在取消操作發(fā)生時觸發(fā),它會返回一個對象届搁,用于撤銷注冊缘薛。

IsCanceled返回trueIsFaulted返回false卡睦。出現(xiàn)OperationCanceledException異常宴胧,任務(wù)進入“已取消”狀態(tài)。

14.6.2 進度報告

有時表锻,異步操作需要在運行時報告進度恕齐。有一種簡單的解決方法是給異步傳入一個 Action 代理,然后進度發(fā)生變化時就會觸發(fā)這個方法:

async void Main()
{
    Action<int> progress = i => Console.WriteLine (i + " %");
    await Foo (progress);
}

 Task Foo (Action<int> onProgressPercentChanged)
{
    return Task.Run (() =>
    {
        for (int i = 0; i < 1000; i++)
        {
            if (i % 10 == 0) onProgressPercentChanged (i / 10);
            // 執(zhí)行CPU綁定代碼.
        }
    });
}
Action<T> 進度

這段代碼運行在控制臺應(yīng)用程序上瞬逊,但是它不適合運行在富客戶端場景显歧,因為它可以從工作者線程報告進度仪或,這可能會給使用者線程帶來線程安全問題。

IProgress<T>Progress<T>

它們的作用是“包裝”一個代理士骤,這樣UI應(yīng)用程序就可以通過同步上下文安全地報告進度范删。

這個接口只定義一個方法:

public interface IProgress<in T>
    {
        // 參數(shù): 
        //   value:
        //     進度更新之后的值。
        void Report(T value);
    }

Iprogress<T>用法很簡單:

Task Foo (IProgress<int> onProgressPercentChanged)
{
    return Task.Run (() =>
    {
        for (int i = 0; i < 1000; i++)
        {
            if (i % 10 == 0) onProgressPercentChanged.Report (i / 10);
            // 執(zhí)行CPU綁定代碼.
        }
    });
}

Progress<T>類有一個構(gòu)造方法拷肌,它可以接受Action<T>類型包裝的代理瓶逃,

    var progress = new Progress<int>(i => Console.WriteLine (i + " %"));
    await Foo (progress);

Progress<T>還有一個ProgressChanged事件,我們可以訂閱這個事件廓块,同時不要給構(gòu)造函數(shù)傳入一個操作代理)

在實例化Progress<int>時厢绝,這個類會波桌同步上下文(如果有)。然后Foo調(diào)用Report時带猴,它會通過上下文調(diào)用代理對象昔汉。

將替換為包含一系列屬性的自定義類型,就可以在異步方法中實現(xiàn)更復(fù)雜的進度報告拴清。

IProgress<T>生成的值一般是“廢棄值”(例如靶病,完成比或已下載字節(jié)),而由IObserver<T>MoveNext生成的值通常由結(jié)果組成口予,這個正式調(diào)用它的初衷娄周。

14.6.3 基于任務(wù)的異步模式(TAP)

一個TAP方法必須:

  • 返回一個“熱”(正在運行)TaskTask<TReuslt>
  • 擁有“Async”后綴
  • 如果支持取消或進度報告,重載可接收取消令牌或IProgress<T>
  • 快速返回調(diào)用者
  • 在I/O 綁定代碼中不占用線程沪停。

14.6.4 任務(wù)組合器

CLR包含兩個任務(wù)組合器:Task.WhenAnyTask.WhenAll煤辨。

我們假定以下方法:

async Task<int> Delay1() { await Task.Delay (1000); return 1; }
async Task<int> Delay2() { await Task.Delay (2000); return 2; }
async Task<int> Delay3() { await Task.Delay (3000); return 3; }

1.WhenAny

當(dāng)任務(wù)組中任意一個任務(wù)完成,它就完成木张。下面任務(wù)會1秒內(nèi)完成:

async void Main()
{
    Task<int> winningTask = await Task.WhenAny (Delay3(), Delay1(), Delay2());
    Console.WriteLine ("Done");
    Console.WriteLine (winningTask.Result);   // 1
}

因為Task.WhenAny本身會返回一個任務(wù)众辨,所以我們要等待它,然后它會返回先完成的任務(wù)舷礼。這個例子完全不會阻塞——包括訪問Result屬性的最后一行語句(因為winningTask已經(jīng)完成)鹃彻。但是,最好還是要等待任務(wù)(winningTask):

Console.WriteLine (await winningTask);   // 1

因為這時任何異常都會重新拋出妻献,而不需要包裝一個AggregateException異常中蛛株。事實上,我們可以進一步操作中同時執(zhí)行兩個await

int answer = await await Task.WhenAny (Delay1(), Delay2(), Delay3());

如果后面沒有一個未完成任務(wù)出現(xiàn)錯誤育拨,那么除非后面等待了這個任務(wù)谨履,否則該異常將不會被捕捉到。

WhenAny適合用于應(yīng)用操作超時時間或取消操作:

async void Main()
{
    Task<string> task = SomeAsyncFunc();                          //返回task
    Task winner = await (Task.WhenAny (task, Task.Delay(5000)));  //返回Task.Delay(5000)
    if (winner != task) throw new TimeoutException();
    string result = await task;   // 解開結(jié)果/重新拋出異常
}

async Task<string> SomeAsyncFunc()
{
    await Task.Delay (10000);
    return "foo";
}

注意這個例子不同類型的任務(wù)去調(diào)用WhenAny至朗,所以完成的任務(wù)報告為一個普通Task(而非Task<string>

2.WhenAll

當(dāng)傳入的所有任務(wù)完成時屉符,它才完成剧浸。下面的任務(wù)會在3秒之后完成(同時演示了分叉/聯(lián)合模式)

 await Task.WhenAll(Delay1(), Delay2(), Delay3());

不使用WnenAll锹引,而依次等待task1,task2和task3矗钟,也可以得到相似的結(jié)果:

    Task task1 = Delay1(),task2  = Delay2(),task3 = Delay3();
    await task1;await task2;await task3;

這種方式,除了三次等待效率低于一次等待外嫌变,區(qū)別:如果task1出錯吨艇,不執(zhí)行task2/task3。而且異常無法處理腾啥。

相反东涡,Task.WhenAll只有在所有任務(wù)完成后才會完成——即使中間出現(xiàn)錯誤。如果出現(xiàn)多個錯誤倘待,它們的異常會組合到任務(wù)的AggregateException之中疮跑。

然而,等待組合的任務(wù)只能捕捉到第一個異常凸舵,所以如果要查看所有異常祖娘,則必須這樣做:

Task task1 = Task.Run (() => { throw null; } );
Task task2 = Task.Run (() => { throw null; } );
Task all = Task.WhenAll (task1, task2);
try { await all; }
catch
{
    Console.WriteLine (all.Exception.InnerExceptions.Count);   // 2 
}    

結(jié)果輸出為:2

使用類型為Task<TResult>的任務(wù)調(diào)用WhenAll,會返回一個Task<TResult[]>啊奄,這是所有任務(wù)的結(jié)果組合渐苏。如果執(zhí)行等待操作時,那么這個結(jié)果會變成TResult[]

    Task<int> task1 = Task.Run (() => 1);
    Task<int> task2 = Task.Run (() => 2);
    int[] results = await Task.WhenAll (task1, task2);   // { 1, 2 }    

下面一個例子菇夸,并行下載多個URI琼富,然后計算它們的總下載大小:

async void Main()
{
    int totalSize = await GetTotalSize ("http://www.qq.com http://www.weibo.com http://www.163.com".Split());
    totalSize.Dump();
}

async Task<int> GetTotalSize (string[] uris)
{
    IEnumerable<Task<byte[]>> downloadTasks = uris.Select (uri => 
    new WebClient().DownloadDataTaskAsync (uri));   
    byte[][] contents = await Task.WhenAll (downloadTasks);
    return contents.Sum (c => c.Length);
}

字段代碼效率不行庄新,我們只能在每一個任務(wù)都完成之后才能處理字節(jié)數(shù)組鞠眉。如果在下載之后馬上將字節(jié)數(shù)組壓縮為實際長度,那么效率會提高择诈。這正式異步lambda發(fā)揮作用地方凡蚜,因為我們在LINQ的Select查詢操作符插入一個await表達式:

async Task<int> GetTotalSize (string[] uris)
{
    IEnumerable<Task<int>> downloadTasks = uris.Select (async uri =>
        (await new WebClient().DownloadDataTaskAsync (uri)).Length);   //await .... Length
        
    int[] contentLengths = await Task.WhenAll (downloadTasks);
    return contentLengths.Sum();
}

3.自定義組合器

編寫自定義的任務(wù)組合很實用。最簡單的組合器可以接受一個任務(wù)吭从,下面例子允許在特定超時時間里等待任意任務(wù):

async void Main()
{
    string result = await SomeAsyncFunc().WithTimeout (TimeSpan.FromSeconds (2));
    result.Dump();
}

async Task<string> SomeAsyncFunc()
{
    await Task.Delay (10000);
    return "foo";
}

//Task<TResult> 擴展方法
public static class Extensions
{
    public async static Task<TResult> WithTimeout<TResult> (this Task<TResult> task, TimeSpan timeout)
    {
        Task winner = await (Task.WhenAny (task, Task.Delay (timeout)));
        if (winner != task) throw new TimeoutException();
        return await task;   // 解開結(jié)果/重新拋出異常
    }
}

下面代碼通過一個CancellationToken“拋棄”一個任務(wù):

public static class Extensions
{
    public static Task<TResult> WithCancellation<TResult> (this Task<TResult> task, CancellationToken cancelToken)
    {
        var tcs = new TaskCompletionSource<TResult>();
        var reg = cancelToken.Register (() => tcs.TrySetCanceled ());
        task.ContinueWith (ant => 
        {
            reg.Dispose();      
            if (ant.IsCanceled)
                tcs.TrySetCanceled();
            else if (ant.IsFaulted)
                tcs.TrySetException (ant.Exception.InnerException);
            else
                tcs.TrySetResult (ant.Result);
        });
        return tcs.Task;    
    }
}

任務(wù)組合器有時候可能很復(fù)雜朝蜘,需要22章介紹的各種信號結(jié)構(gòu)

下面的組合器作用與WhenAll類似涩金,唯一不同的是如果任意任務(wù)出現(xiàn)錯誤谱醇,那么最終任務(wù)也會馬上出錯:

async void Main()
{
    
    Task<int> task2 = Task.Delay (5000).ContinueWith (ant => {return 53;});
    Task<int> task1 = Task.Run (() => {throw null; return 42; } );         //--->未將對象引用為實例
    int[] results = await WhenAllOrError (task1, task2);
}

async Task<TResult[]> WhenAllOrError<TResult> (params Task<TResult>[] tasks)
{
    var killJoy = new TaskCompletionSource<TResult[]>();
    
    foreach (var task in tasks)
         task.ContinueWith (ant =>
        {
            if (ant.IsCanceled) 
                killJoy.TrySetCanceled();  //嘗試將底層Task <TResult>轉(zhuǎn)換為已取消狀態(tài)。
            else if (ant.IsFaulted)
                killJoy.TrySetException (ant.Exception.InnerException);
        });
        
    return await await Task.WhenAny (killJoy.Task, Task.WhenAll (tasks));       
}

這里先創(chuàng)建一個TaskCompletionSource步做,它的唯一作用的終止出錯的任務(wù)(此例)副渴。因此,這里不會調(diào)用它的SetResult方法全度,只會調(diào)用它的TrySetCanceledTrySetException方法煮剧。

這個例子更適合ContinueWith,而不是GetAwaiter().OnCompleted,因為我們不需要訪問任務(wù)的結(jié)果勉盅,也不需要在此彈回UI線程佑颇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市草娜,隨后出現(xiàn)的幾起案子挑胸,更是在濱河造成了極大的恐慌,老刑警劉巖宰闰,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茬贵,死亡現(xiàn)場離奇詭異,居然都是意外死亡移袍,警方通過查閱死者的電腦和手機解藻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葡盗,“玉大人舆逃,你說我怎么就攤上這事〈亮#” “怎么了路狮?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔚约。 經(jīng)常有香客問我奄妨,道長,這世上最難降的妖魔是什么苹祟? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任砸抛,我火速辦了婚禮,結(jié)果婚禮上树枫,老公的妹妹穿的比我還像新娘直焙。我一直安慰自己,他們只是感情好砂轻,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布奔誓。 她就那樣靜靜地躺著,像睡著了一般搔涝。 火紅的嫁衣襯著肌膚如雪厨喂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天庄呈,我揣著相機與錄音蜕煌,去河邊找鬼。 笑死诬留,一個胖子當(dāng)著我的面吹牛斜纪,可吹牛的內(nèi)容都是我干的贫母。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼盒刚,長吁一口氣:“原來是場噩夢啊……” “哼腺劣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伪冰,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎樟蠕,沒想到半個月后贮聂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡寨辩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年吓懈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靡狞。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡耻警,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甸怕,到底是詐尸還是另有隱情甘穿,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布梢杭,位于F島的核電站温兼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏武契。R本人自食惡果不足惜募判,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咒唆。 院中可真熱鬧届垫,春花似錦、人聲如沸全释。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浸船。三九已至符衔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糟袁,已是汗流浹背判族。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留项戴,地道東北人形帮。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辩撑。 傳聞我的和親對象是個殘疾皇子界斜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容