今天我們來看看, 當創建一個 task 物件, 設定完要做的行為已及排程方式後, 會調用兩個方法, 他們分別代表甚麼意思。
以下方法在執行Task.Run
打包新task
調用
internal static Task InternalStartNew(
Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler,
TaskCreationOptions options, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark)
{
// Validate arguments.
if (scheduler == null)
{
throw new ArgumentNullException("scheduler");
}
Contract.EndContractBlock();
// Create and schedule the task. This throws an InvalidOperationException if already shut down.
// Here we add the InternalTaskOptions.QueuedByRuntime to the internalOptions, so that TaskConstructorCore can skip the cancellation token registration
Task t = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
t.PossiblyCaptureContext(ref stackMark);
t.ScheduleAndStart(false);
return t;
}
我們要來看看這兩個方法
t.PossiblyCaptureContext(ref stackMark);
t.ScheduleAndStart(false);
PossiblyCaptureContext
internal void PossiblyCaptureContext(ref StackCrawlMark stackMark)
{
Contract.Assert(m_contingentProperties == null || m_contingentProperties.m_capturedContext == null,
"Captured an ExecutionContext when one was already captured.");
// In the legacy .NET 3.5 build, we don't have the optimized overload of Capture()
// available, so we call the parameterless overload.
#if PFX_LEGACY_3_5
CapturedContext = ExecutionContext.Capture();
#else
CapturedContext = ExecutionContext.Capture(
ref stackMark,
ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase);
#endif
}
可以發現重點是調用 ExecutionContext.Capture
其底層是利用 Thread 中的 method 獲取當前所在 thread 的內容並且回傳。
所以變數 CapturedContext , 經過這個方法後裡面存了 當前 thread 裡面的資料, 待用。
換句話說, task 存下了當初創建他的 thread 的執行內容。
ScheduleAndStart
internal void ScheduleAndStart(bool needsProtection)
{
Contract.Assert(m_taskScheduler != null, "expected a task scheduler to have been selected");
Contract.Assert((m_stateFlags & TASK_STATE_STARTED) == 0, "task has already started");
// 設定任務開始的標記
if (needsProtection)
{
if (!MarkStarted())
{
// A cancel has snuck in before we could get started. Quietly exit.
return;
}
}
else
{
m_stateFlags |= TASK_STATE_STARTED;
}
if (s_asyncDebuggingEnabled)
{
AddToActiveTasks(this);
}
if (AsyncCausalityTracer.LoggingOn && (Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0)
{
//For all other task than TaskContinuations we want to log. TaskContinuations log in their constructor
AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task: " + ((Delegate)m_action).Method.Name, 0);
}
try
{
// 把當前任務送入 Scheduler 的隊列中
m_taskScheduler.InternalQueueTask(this);
}
catch (ThreadAbortException tae)
{
AddException(tae);
FinishThreadAbortedTask(true, false);
}
catch (Exception e)
{
// The scheduler had a problem queueing this task. Record the exception, leaving this task in
// a Faulted state.
TaskSchedulerException tse = new TaskSchedulerException(e);
AddException(tse);
Finish(false);
// Now we need to mark ourselves as "handled" to avoid crashing the finalizer thread if we are called from StartNew()
// or from the self replicating logic, because in both cases the exception is either propagated outside directly, or added
// to an enclosing parent. However we won't do this for continuation tasks, because in that case we internally eat the exception
// and therefore we need to make sure the user does later observe it explicitly or see it on the finalizer.
if ((Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0)
{
// m_contingentProperties.m_exceptionsHolder *should* already exist after AddException()
Contract.Assert(
(m_contingentProperties != null) &&
(m_contingentProperties.m_exceptionsHolder != null) &&
(m_contingentProperties.m_exceptionsHolder.ContainsFaultList),
"Task.ScheduleAndStart(): Expected m_contingentProperties.m_exceptionsHolder to exist " +
"and to have faults recorded.");
m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false);
}
// re-throw the exception wrapped as a TaskSchedulerException.
throw tse;
}
}
可以發現重點在 m_taskScheduler.InternalQueueTask(this);
其把當前任務送入 Scheduler 的隊列中, 而我們之前說過, 這個 Scheduler 其實就是 TP
所以我們繼續往下看
internal void InternalQueueTask(Task task)
{
Contract.Requires(task != null);
task.FireTaskScheduledIfNeeded(this);
this.QueueTask(task);
}
重點在 QueueTask
他其實是一個介面, 擔當了所有類型 Scheduler 推入任務的入口
而因為我們知道 Scheduler 是 TP , 所以直接看 TP 複寫的 QueueTask
protected internal override void QueueTask(Task task)
{
if ((task.Options & TaskCreationOptions.LongRunning) != 0)
{
// Run LongRunning tasks on their own dedicated thread.
Thread thread = new Thread(s_longRunningThreadWork);
thread.IsBackground = true; // Keep this thread from blocking process shutdown
thread.Start(task);
}
else
{
// Normal handling for non-LongRunning tasks.
bool forceToGlobalQueue = ((task.Options & TaskCreationOptions.PreferFairness) != 0);
ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue);
}
}
這裡可以發現兩種 case ,
UnsafeQueueCustomWorkItem
在處理連續任務區有遇到, 可以把任務放入 TP 來執行。此外在ScheduleAndStart
可以看到在例外處理有
兩種 method , 他們是用來表示主要任務完成, 會觸發前面提到的連續任務區。
ScheduleAndStart
要求同步運行時實際上, 在實作場合, 創建一個 Task 但卻希望他完成後別的 Task 才繼續走的的情況非常常見。
其底層就是在 ScheduleAndStart
中調用 Scheduler 時不是使用預設的 TP 作為 Scheduler 而是使用 SynchronizationContextTaskScheduler
類別作為 Scheduler , 以下查看其複寫的InternalQueueTask
。
protected internal override void QueueTask(Task task)
{
m_synchronizationContext.Post(s_postCallback, (object)task);
}
public virtual void Post(SendOrPostCallback d, Object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}
可以看見其一樣是推入 TP 來執行, 但是調用了 WaitCallback
作為參數, 該物件我往下看過, 來自外部程式碼, 推測功能是與同樣來自外部的 TP 底層 Scheduler 溝通, 使外部程式碼操作的 workerThread 用同步方式運行這個 TP 中的任務。
到此, .Net Task 算是看得差不多了, 我明天會進行這幾天文章的整理, 算是做一個懶人包給大家。
明天見 !