Taskを調べてると頻繁に出てくる「コンテキスト」とは何か?

やりたいこと

Taskとか非同期処理を調べていると、あちこちに「コンテキスト」「同期コンテキスト」「非同期コンテキスト」という言葉が出てくる。

これが、調べても調べても何のことを言っているのかわからない。

なんとなく、「コンテキスト ≒ スレッド」なのかな?と感じつつも、説明文の「コンテキスト」を「スレッド」に置き換えて読んでみてみ意味がとおらない。

コンテキストとは何なのか?調べたい。もう調べたのだが、いまだよくわからないので、真実が知りたい。

やったこと

いろいろ調べているうちに、 たまたま、下記のページに出会うことができた。とくに、「いまさら async/await」「async/await と SynchronizationContext 」を拝見して、長年の疑問がすーっと消えていった気がする。

いまさら async/await

https://tech.blog.aerie.jp/entry/2015/08/29/022932

async/await と SynchronizationContext (1)

https://tech.blog.aerie.jp/entry/2015/09/17/110258

async/await と SynchronizationContext (2)

https://tech.blog.aerie.jp/entry/2015/09/17/110425

async/await ~非同期なライブラリは楽じゃない~

https://qwerty2501.hatenablog.com/entry/2014/04/24/235849

(C#) 同期コンテキストと Task, async/await

https://qiita.com/ikorin24/items/92ea46374642a0b59011

まだ100%理解したとは言い難いが、頭の中に浮かんできた現状の理解を、とにかく忘れるまにメモする。

メモ

上のページをみればわかる、といえば一言で終わってしまうので、 現状の理解を言語化してみる。(間違い多々あるかも)

  • SynchronizationContextについて

    • .NET2.0からSynchronizationContextクラスが登場した。それが同期コンテキストと呼ばれる場合がある。
      • 「コンテキスト」とは非同期操作の実行状況や環境に関する情報を含むもの。(by chatGPT)
    • SynchronizationContext は、スレッド毎にインスタンスを持っている。
      • WPFの場合、UIスレッドは持ってる。ワーカースレッドはもってない。
    • SynchronizationContextを使うと、立ち上げたワーカースレッドが実行されるスレッドを、立ち上げた側のスレッドと同じにできたりする。
      • WPFの場合、ワーカースレッドが実行されるスレッドを、UIスレッドと同じにしたりできる。
    • WPFのUIスレッドが自分のSynchronizationContextを取得しようと思うと、SynchronizationContext.Currentから取得できる。
      • ワーカースレッドでは、SynchronizationContext.Currentにはなにも入っていない。
    • WPFではそういう感じだが、別のプラットフォーム、フレームワーク上では、全く別だったりする。(例えばASP.NET)
    • ただ、.NET6時点のTaskやasync/awaitを利用した非同期処理では、直接的にSynchronizationContextを見る機会がない。(使わなくても十分非同期処理できる)
  • Taskについて

    • Taskは、Awaitebleオブジェクトである。
    • Taskは、awaitすると、
      • Taskの処理をしている間は別スレッド(ワーカースレッド)で処理を行って、→①
      • 呼び出し元スレッドに一旦戻り、→②
      • 別スレの処理が終わったら、呼び出し元の処理の続きを呼び出し元スレッドで実行する。→③
    • その動きは、コンパイラが下記のように、処理を分解してくれて実現されてる。
      • タスクに登録された処理を別スレッドで実行する。→①に該当
      • TaskAwaiterを取得 → ★???
      • 自分自身(呼び出し元)の処理のawait以降をコールバックに登録して、いったん処理を終わる →②に該当
        • 自分自身を、await前と後に分解して、後の方をコールバックに登録する
      • タスクの処理が終わったら、登録したコールバック(自分自身の続き)を実行する →③に該当
  • TaskAwaiterについて

    • TaskAwaiterは、Taskの呼び出し元のコンテキストをキャプチャーして、
      • コンテキスト(SynchronizationContext)をキャプチャーする≒そのコンテキストがどのスレッドで行われているか、を覚えておくようなもの
    • Task処理が終わったら、SynchronizationContext.Post経由でコールバック(=呼び出し元処理の続き)を実行する
      • キャプチャーしたコンテキストのSynchronizationContext.Post経由で呼び出し元の処理を呼ぶということは、つまり呼び出し元のスレッドに戻るということ
    • という感じで、awaitの動きを実現している。
      • つまり上の★???でTaskAwaiterを取得したときに、呼び出し元のコンテキストを覚えてるから、Taskの処理が終わった後に、呼び出し元に戻ることができる。
  • SynchronizationContext の使いどころはどこか?

    • 現在のWPFでは、自分でSynchronizationContextをコードに書くことは、今まで私が出会ったことがないような何かが出てこない限り、しなくてよいと理解。
      • ただそれはコンパイラが裏でSynchronizationContextを使って、ええようにしてくれているおかげである。
      • 昔は、処理を別スレッドに投げたときに、UIを更新するためにSynchronizationContextを使って、UIスレッドに戻す、みたいなことをしてたっぽい。
  • .configureAwait(false)は何をしてるか?

    • awaitしたあと、元のスレッドに戻る動きをやめて、ワーカースレッドのほうで、await後の処理を進めるようにするということ。
    • なので、TaskをWaitしてUIスレッドを止めたあとに、Taskの処理終了時に、止まってるUIスレッドに帰ろうとすることが原因のデッドロックは起きなくなる。(UIスレッドに帰らずに、ワーカースレッドでそのまま動くから。)

結論

今のところ、「処理を実行するスレッドを操るためのもの」と理解。

備考

まとまらないが、ざっと現状理解を言葉にしてみた。

参考

上で紹介させていただいたいくつかのページ。

ありがとうございました。