計算限制的異步操作(上)
一、協(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:
- 通過構(gòu)造方法,通過Action或Action<Object>來確定想要執(zhí)行的操作;
- 通過靜態(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;
}