03-計算限制的異步操作(上)

計算限制的異步操作(上)

一、協(xié)作式取消

.Net 對于視圖取消操作的代碼提供了標(biāo)準(zhǔn)的取消模式,稱為協(xié)作式取消。

協(xié)作式取消:要取消的操作必須顯示支持取消;

標(biāo)準(zhǔn)的協(xié)作式取消模式中兩個FCL類型:

  • System.Threading.CancellationTokenSource對象;
  • System.Threading.CalcellationToken對象;

1.1 CancellationTokenSource

該類結(jié)構(gòu)如下:

public sealed class CancellationTokenSource : IDisposable {
    public CancellationTokenSource();
    public void Dispose();  // 釋放資源,如WaitHandle

    public Boolean IsCancellationRequset { get; }
    public CancellationToken Token { get; }

    public void Cancel();
    public void Cancel(Boolean throwOnFirstException);
}

其Token屬性包含對一個或多個CancellationToken實例,并將它傳遞給操作。
對于使用 Token 的 Register 的多個回調(diào),當(dāng)使用 Cancel(Boolean throwOnFirstException) 方法時,對于throwOnFirstException參數(shù):

  • true:發(fā)生異常的第一個回調(diào)方法將阻止其他回調(diào)方法的調(diào)用,異常將從Cancel拋出;
  • false:所有回調(diào)方法都會被調(diào)用,當(dāng)所有回調(diào)執(zhí)行完畢后,Cancel會拋出一個 AggregateException,該異常實例的 InnerExceptions 屬性包含了所有異常對象的集合。

1.2 CancellationToken

該類型是一個結(jié)構(gòu)體類型,為值類型,其常用成員如下:

public struct CancellationToken {
    public static CancellationToken None {get;}

    public Boolean IsCancellationRequested { get; }  // 由通過非Task調(diào)用的操作調(diào)用
    public void ThrowIfCancelltionRequest();        // 由通過Task調(diào)用的操作調(diào)用

    public Boolean CanCanceled { get; }

    // CancellationTokenSource取消時,WaitHandle會收到信號
    public WaitHandle WaitHandle { get; }   
    public CancellationTokenRegistration Register(Action<Object> callBack, 
                                                  Object state, 
                                                  Boolean useSynchronizationContext);
}

該類型的實例包含一個對CancellationTokenSource對象引用的私有字段。在代碼中可定時調(diào)用 IsCancellationRequested 屬性判斷當(dāng)前操作是否需要提前退出。

通過 CancellationToken.None 這個靜態(tài)屬性,可返回一個不和任何 CancellationTokenSource 對象相關(guān)聯(lián)的Token(該Token的對CancellationTokenSource 引用的私有字段為null)。其 CanBeCancelled 始終為false,而通過CancellationTokenSource返回的Token,該屬性始終為true。

可調(diào)用 CancellationToken 的Register方法注冊一個或多個將在關(guān)聯(lián)的額CancellationTokenSource被取消時要調(diào)用的方法。

/// <param name="callBack">CancellationTokenSource取消時回調(diào)委托</param>
/// <param name="state">通過委托傳給回調(diào)方法的狀態(tài)值</param>
/// <param name="useSyncContext">是否使用調(diào)用線程的 SynchronizationContext 來調(diào)用委托</param>
public CancellationTokenRegistration Register(Action<Object> callBack, 
                                              Object state,  Boolean useSyncContext);

對于參數(shù)useSynchronizationContext:

  • false:使用調(diào)用Cancel的線程,順序調(diào)用已登記的所有方法;
  • true:已登記的回調(diào)方法會被 send 給已捕捉的 SynchronizationContext 對象,后者決定哪個線程調(diào)用回調(diào);
    • send:調(diào)用線程堵塞,等待在目標(biāo)線程處理完畢后才會返回,相當(dāng)于同步調(diào)用;
    • post:將操作post到一個隊列,調(diào)用線程會立即返回,相當(dāng)于異步調(diào)用。

對于返回值 CancellationTokenRegisteation 對象,存在一個Dispose方法,可以清楚Token上所登記的所有回調(diào)方法。當(dāng) CancellationTokenSource 被 Cancel 時,任何回調(diào)方法都不會被調(diào)用。

1.3 CancellationTokenSource 的鏈接操作

// 創(chuàng)建兩個CancellationTokenSource
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();

cts1.Token.Register(()=> Console.WriteLine("cts1 canceled."));
cts2.Token.Regsiter(()=> Console.WriteLine("cts2 canceled."));

// 創(chuàng)建一個新的CancellationTokenSource,它在cts1或cts2被取消時取消
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
linkedCts.Token.Register(()=> Console.WriteLine("linkedCts canceled."));

// 取消其中一個CancellationTokenSource對象
cts2.Cancel();

// 顯示哪個CancellationTokenSource對象被取消了
Console.WriteLine("cts1 canceled={0}, cts2 canceled={1}, linkedCts={2}", 
cts1.IsCancellationRequested,cts2.IsCancellationRequested,linkedCts.IsCancellationRequested);

以上代碼輸出為:

linkedCts canceled
cts2 canceled
cts1 canceled=false, cts2.canceled=true, linkedCts=true

1.4 定時取消

可以通過以下方法構(gòu)建一個定時自動取消的 CancellationTokenSource 對象,或調(diào)用 CancelAfter 方法在指定時間后自動取消:

public CancellationTokenSource(Int32 millisecondDelay);
public CancellationTokenSource(TimeSpan delay);
public void CancelAfter(Int32 millisecondDelay);
public void CancelAfter(TimeSpan delay);

二、任務(wù)

可使用兩種方式創(chuàng)建一個Task:

  1. 通過構(gòu)造方法,通過Action或Action<Object>來確定想要執(zhí)行的操作;
  2. 通過靜態(tài)Run方法,通過Action或Func>TResult<來確定想要執(zhí)行的操作;

無論是構(gòu)造器還是Run方法,都可以選擇一個CancellationToken,它可以讓Task能夠在調(diào)度前取消。
在構(gòu)造Task時,可以選擇向構(gòu)造器傳遞一些 TaskCreationOptions 標(biāo)志來控制 Task 的執(zhí)行方式,其定義如下:

[Flags, Serializable]
public enum TaskCreationOptions {
    None                = 0x0000,   // Default

    PreferFairness      = 0x0001,   // 【提議】TaskScheduler,希望任務(wù)盡快執(zhí)行
    LongRunning         = 0x0002,   // 【提議】TaskScheduler,盡可能創(chuàng)建線程池線程
    
    AttachedToPrent     = 0x0004,   // 【設(shè)置】附加到它的父Task
    DenyChildAttach     = 0x0008,   // 【設(shè)置】拒絕任何子任務(wù)附加
    HideScheduler       = 0x0010    // 【設(shè)置】使用默認(rèn)TaskScheduler,而不是父任務(wù)的Scheduler
}

TaskCreationOptions控制的是任務(wù)調(diào)度器TaskScheduler對Task的操作行為,對于TaskScheduler相關(guān)的設(shè)置,TaskScheduler可能會也可能不會采納,而后三項只和Task自身相關(guān),總是有效的。

2.1 等待任務(wù)完成并獲取結(jié)果

對于通過構(gòu)造器創(chuàng)建完畢的Task對象,可調(diào)用Start()方法來開啟任務(wù)。
可以通過調(diào)用Wait方法或Result屬性來堵塞調(diào)用線程等待任務(wù)執(zhí)行完畢。

Result屬性內(nèi)部會調(diào)用 Wait;

如果Task還沒有開始執(zhí)行,系統(tǒng)可能(取決于TaskScheduler)使用調(diào)用Wait的線程來執(zhí)行Task。在這種情況下,調(diào)用Wait的線程不會堵塞;它會執(zhí)行Task并立即返回,好處在于,沒有線程會被堵塞,所以減少了對資源的占用(因為不需要創(chuàng)建一個線程來替代被堵塞的線程),并提升了性能(因為不需要花時間創(chuàng)建線程,也沒有上下文切換)。不好的地方在于,假如線程在調(diào)用Wait前已經(jīng)獲得了一個線程同步鎖,而Task視圖獲取同一個鎖,就會造成死鎖的線程!

以上摘自《via clr c#》中的原話,此處并不理解。

Task既可以使用實例方法 Wait 來等待一個單個任務(wù),也可以使用靜態(tài)方法等待一個Task數(shù)組:

  • int Task.WaitAny(params Task[] tasks);
    • 堵塞調(diào)用線程,直到數(shù)組中任一任務(wù)完成,方法即返回;
    • 返回值為數(shù)組索引,若發(fā)生超時則返回-1;
  • Boolean Task.WaitAll(params Task[] tasks);
    • 堵塞調(diào)用線程,直到數(shù)組中所有任務(wù)完成,方法返回;
    • 所有任務(wù)完成返回true,發(fā)生超時返回false;

以上兩個方法若通過一個CancellationToken取消,都會拋出一個OperationCancelledException;

2.2 任務(wù)中的異常處理

默認(rèn)情況下,Task代碼中拋出的異常會被“吞噬”并存儲到一個AggregateException對象中,線程池線程可以返回到線程池中;而當(dāng)Task調(diào)用Wait系方法或Result屬性來等待任務(wù)執(zhí)行過程中,若任務(wù)代碼發(fā)生了異常,則Wait系方法或Result屬性將拋出一個System.AggregateException對象。

Task“吞噬”掉的異常可以向TaskScheduler的靜態(tài)UnobervedTaskException事件等級一個回調(diào)方法,當(dāng)這個Task會GC回收時,CLR的終結(jié)器線程就會引發(fā)這個事件:

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
private void TaskScheduler_UnobservedTaskException(object sender, 
                                                   UnobservedTaskExceptionEventArgs e)
{
      UnhandledExceptionOccurred(e.Exception);
      e.SetObserved();
}

AggregateExceotion中包含一個InnerExceptions屬性,該屬性返回一個ReadOnlyCollection<Exception>集合對象(若父任務(wù)包含多個子任務(wù),多個子任務(wù)中都拋出了異常,則集合中可能會包含多個對象)。
AggregateException提供的常用成員有:

  • 重寫了Exception的GetBaseException方法:
  • Flatten方法:返回一個新的AggregateException,其InnerException屬性是通過原始的AggregateException的內(nèi)層異常層次結(jié)構(gòu)而生成的。
  • Handle方法:為AggregateException中包含的每個異常都調(diào)用一個回調(diào)方法,回調(diào)方法可以為每個異常決定如何對其進(jìn)行處理,回調(diào)方法的返回值表示該異常已處理或未處理;

調(diào)用Handle后,如果至少存在一個未處理異常,就拋出一個新的AggregateException,其中只包含未處理的異常。

2.3 取消任務(wù)

在Task創(chuàng)建的時候傳入一個CancellationToken,將Task和Token進(jìn)行關(guān)聯(lián)。若Task未啟動時被取消,那么Task永遠(yuǎn)不會完成,其Status屬性為 Cenceled,同時,其IsCompleted屬性為true,標(biāo)識該任務(wù)已經(jīng)被完成。此時若再調(diào)用Start()方法來開啟任務(wù),則會拋出一個 InvalidOperationException 異常,無法開啟一個已完成的方法。

CancellationTokenSource cts = new CancellationTokenSource();
Task task = new Task(() => { Sum(10000, cts.Token); }, cts.Token);
cts.Cancel();
// 這里會拋出InvalidOperationException
task.Start();

在Task啟動之后想要取消任務(wù),則必須顯示支持取消,將CancellationToken作為參數(shù)傳遞給回調(diào)方法(或使用Lambda表達(dá)式)。

Task對象雖然關(guān)聯(lián)了CancellationToken但沒有辦法訪問它。

完整的取消Demo如下:

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    Task<int> task = new Task<int>(() => Sum(10000000, cts.Token), cts.Token);
    task.Start();

    cts.Cancel();
    try {
        // 若顯示取消時,任務(wù)還未完成,Result會拋出一個AggregateException
        Console.WriteLine("The sum is:" + task.Result);
    }
    catch (AggregateException ex) {
        // 將所有 OperationCanceledException 都視為已處理
        // 其它任何異常在Handle中都會拋出一個AggregateException,其中只包含未處理的異常
        ex.Handle(e => e is OperationCanceledException);
        Console.WriteLine("Sum was canceled.");
    }
    Console.ReadLine();
}

public static int Sum(int n, CancellationToken token)
{
    int sum = 0;
    for (; n > 0; n--)
    {
        if (token.IsCancellationRequested) break;
        checked { sum += n; };
        // checked 溢出時拋出異常關(guān)鍵字
    }
    return sum;
}

2.4 任務(wù)完成時啟動新任務(wù)

在任務(wù)未完成時調(diào)用Wait方法,極有可能造成線程池創(chuàng)建新線程。

ContinueWith用來注冊當(dāng)前Task執(zhí)行完畢的后續(xù)任務(wù)。Task對象可多次調(diào)用ContinueWith,這樣Task完成后,所有ContinueWith任務(wù)都會進(jìn)入線程池隊列中,使用線程池線程來完成后續(xù)任務(wù)。

  • ContinueWith會返回一個新的Task對象來代表當(dāng)前的任務(wù);
  • 其方法參數(shù)委托中引用了其前置Task,可以獲取前置Task的執(zhí)行情況。
  • 可以傳入TaskContinuationOptions來指定當(dāng)前Task的執(zhí)行條件;

TaskContinuationOptions定義如下:

[Flags, Serializable]
public enum TaskContinuationOptions {
    None                        = 0x0000,   // 默認(rèn)
    PreferFairness              = 0x0001,   // 提議TaskScheduler盡快執(zhí)行任務(wù)
    LongRunning                 = 0x0002,   // 提議TaskScheduler盡可能創(chuàng)建線程池線程
    AttachedToPrent             = 0x0004,   // 將當(dāng)前Task和它的父Task關(guān)聯(lián)
    DenyChildAttach             = 0x0008,   // 禁止關(guān)聯(lián)子任務(wù),否則拋出InvalidOperationExcetion
    HideScheduler               = 0x0010,   // 強迫子任務(wù)使用默調(diào)度器,而不是父任務(wù)的調(diào)度器

    LazyCancellation            = 0x0020,   // 除非前置任務(wù)完成,否則禁止延續(xù)任務(wù)完成
    ExecuteSynchronously        = 0x80000,  // 由執(zhí)行前置任務(wù)的線程來完成當(dāng)前延續(xù)任務(wù)
    // 指明在什么情況下允許運行ContinueWith任務(wù)
    NotOnRanToCompletion        = 0x10000,  
    NotOnFaulted                = 0x20000,
    NotOnCanceled               = 0x40000,
    OnlyOnCanceled              = NotOnRanToCompletion | NotOnFaulted,
    OnlyOnFaulted               = NotOnRanToCompletion | NotOnCanceled,
    OnlyOnRanToCompletion       = NotOnFaulted | NotOnCanceled
}

由于ContinueWith同時創(chuàng)建了一個新的Task,故TaskContinueationOptions也提供了TaskCreationOptions所有的選項,來設(shè)置新建的Task對象。枚舉的其它選項值聲明了ContinueWith任務(wù)執(zhí)行的先決條件。
其中Task“完成”的各種狀態(tài)如下:

  • Completion:任務(wù)執(zhí)行成功,沒有取消也沒有發(fā)生異常;
  • Faulted:任務(wù)執(zhí)行失敗,執(zhí)行任務(wù)期間發(fā)生異常;
  • Canceled:任務(wù)取消,執(zhí)行任務(wù)過程中被顯式終止;
Task<Int32> t = Task.Run(()=> Sum(10000));
t.ContinueWith(task => Console.WriteLine("The sum is: " + task.Result), 
               TaskContinuationOptions.OnlyOnRanToCompletion);
t.ContinueWith(task => Console.WriteLine("Sum threw: " + task.Exception.InnerException), 
               TaskContinuationOptions.OnlyOnFaulted);
t.ContinueWith(task => Console.WriteLine("Sum was canceled."), 
               TaskContinuationOptions.OnlyOnCanceled);

2.5 父子任務(wù)

任務(wù)支持父/子關(guān)系,在父任務(wù)的回調(diào)方法中可以創(chuàng)建新任務(wù),并使用TaskCreationOptions將新任務(wù)指定為子任務(wù)。
只有所有子任務(wù)完成時,父任務(wù)才算結(jié)束。

Task<int[]> parentTasks = new Task<int[]>(() => {
    int[] results = new int[3];
    // Task<int[]> 父任務(wù)內(nèi)部生成了3個子任務(wù)
    Action action0 = new Action(() => results[0] = Sum(CancellationToken.None, 10000));
    Action action1 = new Action(() => results[1] = Sum(CancellationToken.None, 20000));
    Action action2 = new Action(() => results[2] = Sum(CancellationToken.None, 300000));
    new Task(action0, TaskCreationOptions.AttachedToParent).Start();
    new Task(action1, TaskCreationOptions.AttachedToParent).Start();
    new Task(action2,TaskCreationOptions.AttachedToParent).Start();

    return results;
});
// 所有子任務(wù)完成,父任務(wù)才算完成
parentTasks.ContinueWith(parent => {
    try {
        // 任何一個子任務(wù)拋出異常,父任務(wù)都不算完成,查詢Result屬性報錯
        Array.ForEach(parent.Result, Console.WriteLine);
    }
    catch (AggregateException ex) {
        Console.WriteLine(ex.GetBaseException().Message);
    }
    Console.WriteLine("Parent completed status is " + parent.Status);
});
// 啟動父任務(wù)
parentTasks.Start();

輸出結(jié)果為

算術(shù)運算導(dǎo)致溢出。
Parent completed status is Faulted

2.6 任務(wù)內(nèi)部揭秘

每個Task對象都有構(gòu)成任務(wù)狀態(tài)的一組字段(9部分):

  • Id;
  • Status:任務(wù)狀態(tài);
  • 父任務(wù)的引用;
  • 創(chuàng)建Task時指定的TaskScheduler的引用;
  • 對回調(diào)方法的引用;
  • AsyncState:對要傳給回調(diào)方法的對象的引用
  • ExcutionContext的引用;
  • ManualResetEvenSlim對象的引用;
  • 補充狀態(tài)的引用:
    • CancellationToken;
    • ContinueWithTask對象集合;
    • 拋出未處理異常的子任務(wù)準(zhǔn)備的Task對象集合;

使用Task需要為這些狀態(tài)分配內(nèi)存,所以盡量使用ThreadPool.QueueUserWorkItem來節(jié)省資源;

Task和Task<T>對象都實現(xiàn)了IDisposable接口,默認(rèn)Dispose方法都是關(guān)閉ManualResetEventSlim對象。不建議顯示調(diào)用Dispose,應(yīng)該讓GC自己完成。

public enum TaskStatus {
    //Task對象被創(chuàng)建
    Created = 0,
    //該任務(wù)正在等待 .NET Framework 基礎(chǔ)結(jié)構(gòu)在內(nèi)部將其激活并進(jìn)行計劃。
    WaitingForActivation = 1,
    
    WaitingToRun = 2,       //Task已經(jīng)啟動,但尚未開始執(zhí)行。
    Running = 3,            //該任務(wù)正在運行,但尚未完成。

    //該任務(wù)已完成執(zhí)行,正在隱式等待附加的子任務(wù)完成。
    WaitingForChildrenToComplete = 4,

    RanToCompletion = 5,    //已成功完成執(zhí)行的任務(wù)。
    Canceled = 6,           //任務(wù)被取消。
    Faulted = 7             //由于未處理異常的原因而完成的任務(wù)。
}

Task狀態(tài)說明:

  • IsCompleted只讀屬性:
    • RanToCompletion | Canceled | Faulted 狀態(tài);
  • WaitingForActivation(該狀態(tài)意味著Task的調(diào)度由任務(wù)的基礎(chǔ)結(jié)構(gòu)控制):
    • 通過調(diào)用 ContinueWith、ContinueWhenAll、ContinueWhenAny 方法創(chuàng)建的Task;
    • 通過調(diào)用 FromAsync 方法創(chuàng)建的Task;
    • 通過構(gòu)造 TaskCompletionSource<TResult> 對象創(chuàng)建的Task;

Task出錯時,其Exception屬性返回一個AggregateException對象,其InnerExceptions集合包含了所有未處理的異常。

2.7 任務(wù)工廠

TaskFactory用來創(chuàng)建一組共享相同配置的Task對象。要向TaskFactory傳遞希望任務(wù)具有的 CancellationToken、TaskScheduler、TaskCreationOptions和TaskCpntinuationOptions等設(shè)置。

private static void Demo()
{
    Task parent = new Task(() => {
        CancellationTokenSource cts = new CancellationTokenSource();
        // 所有通過TaskFactory啟動的任務(wù)都是子任務(wù),且使用父任務(wù)的線程同步執(zhí)行
        TaskFactory<int> tf = null;
        tf = new TaskFactory<int>(cts.Token, TaskCreationOptions.AttachedToParent, 
                                             TaskContinuationOptions.ExecuteSynchronously, 
                                             TaskScheduler.Default);

        // 該任務(wù)工廠創(chuàng)建并啟動了3個子任務(wù)
        Task<int>[] childTasks = new Task<int>[] {
            tf.StartNew(()=> Sum(cts.Token, 10000)),
            tf.StartNew(()=> Sum(cts.Token, 20000)),
            tf.StartNew(()=> Sum(cts.Token, Int32.MaxValue)) // 將拋出OverFlowException
        };

        // 任何子任務(wù)拋出異常,就取消其余子任務(wù)
        for (int t = 0; t < childTasks.Length; t++) {
        TaskContinuationOptions continueOnFailed = TaskContinuationOptions.OnlyOnFaulted;
            childTasks[t].ContinueWith(task => cts.Cancel(), continueOnFailed);
        }

        // 所有子任務(wù)完成后,從成功完成的任務(wù)中找到返回的最大值,再將該值傳給另一個任務(wù)來顯示最大結(jié)果
        Func<Task<int>[], int> continueFunc = tasks => {
            var completedTasks = tasks.Where(t => !t.IsFaulted && !t.IsCanceled);
            return completedTasks.Max(item => item.Result);
        };
        // 當(dāng)所有子任務(wù)都完成后創(chuàng)建一個Task,由于它是TaskFactory創(chuàng)建的,仍視為TaskFactory子任務(wù)
        Task<int> completedTask = tf.ContinueWhenAll(childTasks, continueFunc, 
                                                     CancellationToken.None);
        completedTask.ContinueWith(t => Console.WriteLine("The maximum is " + t.Result),
                                    TaskContinuationOptions.ExecuteSynchronously);
    });

    // 子任務(wù)完成后,也將顯示任何未處理的異常
    parent.ContinueWith(p => {
        // 將所有文本放到一個StringBuilder中,就只用調(diào)用Console.WriteLine一次,
        // 因為這個任務(wù)可能和上面的任務(wù)并行執(zhí)行,而我不希望任務(wù)的輸出變得不連續(xù)
        string msg = "The following exception(s) occurred:" + Environment.NewLine;
        StringBuilder sb = new StringBuilder(msg);

        foreach (var e in p.Exception.Flatten().InnerExceptions) {
            sb.AppendLine(" " + e.GetType().ToString());
        }
        Console.WriteLine(sb.ToString());
    }, TaskContinuationOptions.OnlyOnFaulted);

    // 啟動父任務(wù),使它能夠啟動子任務(wù)
    parent.Start();
}

使用TaskFactory創(chuàng)建的所有任務(wù)都具有相同的配置,故ft.ContinueWhenAll返回的仍然是父任務(wù)的一個子任務(wù),會用默認(rèn)的TaskScheduler同步執(zhí)行。通過向其傳遞CancellationToken.None來覆蓋TaskFactory的CancellationToken,使其不能取消。

TaskFactory或TaskFactory<TResult>的靜態(tài)ContinueWhenAll和ContinueWhenAny方法,會等待所有已創(chuàng)建的子任務(wù)完成后新建一個延續(xù)任務(wù),該延續(xù)任務(wù)無論每個子任務(wù)的完成狀態(tài)(Completion、Fault or Cancel)是怎樣的都會執(zhí)行。所以 TaskContinuationOption的以下標(biāo)志是非法的:NotOnRanToCompletion, NotOnFaulted, NotOnCanceled,以及它們的組合標(biāo)志。

2.8 任務(wù)調(diào)度器

TaskScheduler對象負(fù)責(zé)執(zhí)行被調(diào)度的任務(wù),同時向VS調(diào)試器公開任務(wù)信息。
FCL提供了兩個派生自TaskScheduler的類型:

  • 線程池任務(wù)調(diào)度器(Thread Pool Task Scheduler);
    • 是所有應(yīng)用程序的默認(rèn)調(diào)度器,負(fù)責(zé)將任務(wù)調(diào)度給線程池的工作者線程。
    • 通過TaskScheduler.Default獲得引用;
  • 同步上下文任務(wù)調(diào)度器(Synchronization Context Task Scheduler):
    • 適合提供了圖形用戶界面的應(yīng)用程序,如Winform、 WPF、SilverLight等;
    • 將所有任務(wù)都調(diào)度給GUI線程,使任務(wù)代碼能夠成功更新UI組件;
    • 該調(diào)度器不使用線程池;
    • 通過TaskScheduler.FromCurrentSynchronizationContext方法獲得引用;

注意,同步上下文調(diào)度器實際上使任務(wù)代碼放到GUI線程的隊列中,并沒有開啟新的線程,它為Task更新UI提供了一種方式。
同時,線程池線程代碼不能嘗試更新UI組件,否則會拋出 InvalidOperationException。
如果有特殊任務(wù)調(diào)度需求,也可以自定義TaskScheduler派生類來完成需求。

// SynchronizationContextTaskScheduler實際安排任務(wù)在主線程執(zhí)行
private readonly TaskScheduler m_syncContextTaskScheduler;
public MainWindow() {
    InitializeComponent();

    m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    txt.Text = "Synchronization Context Task Scheduler Demo";
    Visibility = Visibility.Visible;

    UpdateTime();
}

private CancellationTokenSource m_cts = null;
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
    if (m_cts != null) { // 一個操作正在進(jìn)行,取消它
        m_cts.Cancel();
        m_cts = null;
    }
    else {   // 操作沒有開始,啟動它
    
        txt.Text = "Operation running";
        m_cts = new CancellationTokenSource();

        // 這個任務(wù)使用默認(rèn)TaskScheduler,在一個線程池線程上執(zhí)行
        Task<Int32> task = Task.Run(() => Sum(m_cts.Token, 20000), m_cts.Token);

        // 這些任務(wù)使用同步上下文任務(wù)調(diào)度器,在 GUI 線程上執(zhí)行
        task.ContinueWith(t => txt.Text = "Result: " + t.Result, CancellationToken.None, 
                            TaskContinuationOptions.OnlyOnRanToCompletion, 
                            /*m_syncContextTaskScheduler*/TaskScheduler.Default);

        task.ContinueWith(t => txt.Text = "Operation Canceled.", CancellationToken.None, 
                            TaskContinuationOptions.OnlyOnCanceled, 
                            /*m_syncContextTaskScheduler*/TaskScheduler.Default);

        task.ContinueWith(t => txt.Text = "Operation Faulted.", CancellationToken.None, 
                            TaskContinuationOptions.OnlyOnFaulted, 
                            m_syncContextTaskScheduler);
    }
    base.OnMouseLeftButtonDown(e);
}

public static int Sum(CancellationToken token, int n) {
    int sum = 0;
    for (; n > 0; n--) {
        if (token.IsCancellationRequested) break;
        checked { sum += n; };
        Thread.Sleep(5);
    }
    return sum;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,707評論 2 370

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