【C#】Task の話 その 2
はじめに
前回に引き続きソースコードを読んだり色々してみますよ、というお話。
Task クラスのコードを読む
Task クラスは System.Private.CoreLib.dll に含まれています。
その中で今回呼んでいた Task.Run は下記です。
Task.cs
~省略~ public static Task Run(Action action) { return Task.InternalStartNew(null, action, null, default, TaskScheduler.Default, TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None); } ~省略~
ここでは InternalStartNew を呼んでいるだけのようです。
Task.cs
~省略~ internal static Task InternalStartNew( Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions) { if (scheduler == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); Task task = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler); task.ScheduleAndStart(false); return task; } ~省略~
ここでは 1.Task クラスのインスタンスを作り、 2.Task.ScheduleAndStart を呼んでいます。
これだけ見ると、タスクを作り、スケジュールに追加して実行されるのを待つ、ということのように見えます。
あれ・・・?この話どこかで見たような・・・?
Task クラスのコンストラクタ
Task.cs
internal Task(Delegate action, object state, Task parent, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) { if (action == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action); } // Keep a link to the parent if attached if (parent != null && (creationOptions & TaskCreationOptions.AttachedToParent) != 0) { EnsureContingentPropertiesInitializedUnsafe().m_parent = parent; } TaskConstructorCore(action, state, cancellationToken, creationOptions, internalOptions, scheduler); Debug.Assert(m_contingentProperties == null || m_contingentProperties.m_capturedContext == null, "Captured an ExecutionContext when one was already captured."); CapturedContext = ExecutionContext.Capture(); }
- 今回は( parent が null なので)スキップされますが、ある Task を別の Task の子にすることができるようです。
- TaskConstructorCore といういかにも初期化してそうな名前のメソッドでは、(今回は)下記の変数に値をセットしています。
スラスラわかる C# にて、 Task は ThreadPool を扱いやすくしたもの、といった話が登場していましたが、ついに関連しそうな名前が出てきました。
最後の CapturedContext 、 ExecutionContext も気になるところですね。
ExecutionContext
Microsoft Docsによると 実行中のスレッドに関するすべての情報(セキュリティコンテキスト、論理呼び出しコンテキスト、同期コンテキストなど)を持つコンテナを提供する、とのこと。
この辺りの説明がわかりやすそうな気がします(まだあまりよくわかってない)。
ここでやっていることは、Thread.CurrentThread.ExecutionContext の値が null でも Default (ExecutionContext(false)) でもない場合、 ContingentProperties の m_capturedContext に値がセットされる、ということのようです。
これの意味は ExecutionContext などがわかってくると理解できるものなのでしょうか。。。?
ScheduleAndStart
さあ初期化もできたところでスケジュールしてスタートしましょう。
という ScheduleAndStart ですが、今回知りたい一番重要そうな部分は m_taskScheduler.InternalQueueTask(this); です。
先ほど登場した、 ThreadPoolTaskScheduler に InternalQueueTask があるはずなので追ってみましょう。
…と思ったらありませんでした/(^o^)\
ThreadPoolTaskScheduler クラスは TaskScheduler クラスを継承しているので、そちらを見てみることにします。
TaskScheduler.cs
~省略~ internal void InternalQueueTask(Task task) { Debug.Assert(task != null); #if CORECLR if (TplEtwProvider.Log.IsEnabled()) { task.FireTaskScheduledIfNeeded(this); } #endif this.QueueTask(task); } ~省略~
「#if CORECLR 」というのは .NET Core で CoreCLR 上で実行されると有効になるのでしょうか。。。?
ともあれ QueueTask です。
これは派生クラスで override されています。
ThreadPoolTaskScheduler.cs
~省略~ protected internal override void QueueTask(Task task) { TaskCreationOptions options = task.Options; if ((options & TaskCreationOptions.LongRunning) != 0) { // Run LongRunning tasks on their own dedicated thread. RuntimeThread thread = RuntimeThread.Create(s_longRunningThreadWork); thread.IsBackground = true; // Keep this thread from blocking process shutdown thread.Start(task); } else { // Normal handling for non-LongRunning tasks. bool preferLocal = ((options & TaskCreationOptions.PreferFairness) == 0); ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal); } } ~省略~
おお!? thread.Start とか書いてるぞ?と思ったら、今回は実行されないようです( options が false )。
それはそれとして、ついに ThreadPool クラスが登場しましたね。
引数の算出に使用されている、 TaskCreationOptions についてはこちらを参照。
面白くなってきたところでいったん切ります。
参照
- dotnet/coreclr - GitHub
- Task Class (System.Threading.Tasks) - Microsoft Docs
- Concurrency in .NET - Manning
- スラスラわかる C#