Thread affinity
And
CPS
2016.05.20 CONTINUATION STUDY 2016 KOUJI MATSUI (@KEKYO2)
自己紹介
けきょ (@kekyo2, www.kekyo.net)
ロードバイク乗り
Microsoft MVP for Visual Studio and Development Technology
認定スクラムマスター・スクラムプロダクトオーナー
Center CLRオーガナイザー
C#, F#, C++
おことわり
名古屋から来たアホです。なごやこわい界隈に生息してますが、
メンツには含まれていません(たぶん)
継続の学問的な知識はほとんど無いです。
◦ そのため、学問的厳密性は無いです。
◦ ぶっちゃけ、CPS的に画期的な話ではないと。
◦ スイートスポットにはまる人には、面白いかも知れません(多分いない)
アジェンダ
ユーザーインターフェイスとスレッド親和性
UIキューの隠蔽
非同期処理との融合
ユーザーインターフェイス
ユーザーインターフェイススレッド
Start
Message pumps UI Queue
Update
handler
Handler
Click
handler
Button
clicked
UI thread context
処理をオフロードする
ユーザーインターフェイススレッド vs ワーカースレッド
Start
Message pumps UI Queue
Update
handler
Handler
Click
handler
Start
Offload workings
Race
condition
Worker thread
context
処理をオフロードする
リソース競合
Handler start
Replace textbox
Worker thread
context
Modify UI states
Race condition
Race condition
スレッド親和性 (Thread affinity)
全てのUI部品やUIハンドラは、特定のスレッドで動作すること
を前提としています。
特定のスレッドに紐づいているUI部品の、スレッド依存の状態
を「スレッド親和性」と呼びます。
スレッド親和性を満たす
UIキューを介して通信を行う
Start
Message pumps UI Queue
Handler Handler Handler
Start
Offload workings
Worker thread
context
UI thread context
スレッド親和性を満たす
ハンドラとオフロード処理の関係
Start
Message pumps UI Queue
Update
handler
Handler
Click
handler
Start
Offload workings
ハンドラの遷移
Click handler
Offload workings
Update handler
UI Queue
Worker thread
context
UI thread context
UI thread context
UI Queue
ハンドラの遷移
Click handler
Offload workings
Update handler
UI Queue
UI Queue
ハンドラの遷移
Click handler
Offload workings
Update handler
ハンドラの遷移
Click handler
Offload workings
Update handler
ハンドラの遷移
Click handler
Offload workings
Update handler
ハンドラの遷移
Click handler
Offload workings
Update handler
何となく見えてきた?
アジェンダ
ユーザーインターフェイスとスレッド親和性
UIキューの隠蔽
非同期処理との融合
UIキューを隠ぺいする
Click handler
Offload workings
Update handler
UI Queue
UI Queue
ワーカースレッドからUIスレッドに跨ぐときに
UIキューが絡む
ワーカースレッド起動
UIキューを隠ぺいする
UIキューに要求(ワーカースレッド起動)を入れ、結果をUI
キューから得るような f を考える。
let uiq = new Queue<(unit -> ‘U)>()
let f (action: Func<‘T, ‘U>) (argument: ‘T) (cps: ‘U -> unit) =
Thread.Execute(fun _ ->
let result = action(argument)
uiq.Enqueue result)
while true do
let result = uiq.Dequeue()
cps result
ワーカースレッドの起動
継続を実行
.NETにおいてのワーカースレッド
.NET標準のワーカースレッド起動は Task.Run() メソッド。
Task.Run() は Task<‘U> を返す。
TaskにはCPSを仕込む Task.ContinueWith() メソッドがある。
let uiq = new Queue<(unit -> ‘U)>()
let f (action: Func<‘T, ‘U>) (argument: ‘T) (cps: ‘U -> unit) =
let task = Task.Run<‘U>(fun _ -> action(argument))
task.ContinueWith(fun t -> uiq.Enqueue t.Result)
while true do
let result = uiq.Dequeue()
cps result
継続を実行
ワーカースレッド完了時に
キューに入れる
DispatcherSynchronizationContext
.NETにおいてのUIキュー
.NET標準の同期化キューは SynchronizationContext クラス (SynchContext)。
SynchContext.Post() は、スレッド親和性が必要な場合に呼び出される。
CPSを渡せば必要なスレッドで継続を実行することを「想定」できる。
SynchContextの実装は、クラスを継承して作る必要がある。
◦ Windows FormsはWinFormsSynchronizationContext
◦ WPFはDispatcherSynchronizationContext
◦ を使えばいい(自動的に使われる)
UI Queue
Post()
SynchronizationContext
.NETにおいてのUIキュー
let f (action: Func<‘T, ‘U>) (argument: ‘T) (cps: ‘U -> unit) =
let task = Task.Run<‘U>(fun _ -> action(argument))
task.ContinueWith(fun t -> SynchContext.Current.Post(
(fun _ -> cps t.Result), null))
ワーカースレッド完了時に
SynchContextにPostする
インフラとしてのSynchContext
実はSynchContextは.NETの奥底で統合されています。
◦ 現在のスレッドに対応するSynchContextは、SynchContext.Currentでアクセス
できます。
Task.ContinueWithによるCPSも、Currentのインスタンスを自動的に
使います。
なので、前述のような明示的な Post は必要ありません。
let f (action: Func<‘T, ‘U>) (argument: ‘T) (cps: ‘U -> unit) =
let task = Task.Run<‘U>(fun _ -> action(argument))
task.ContinueWith(fun t -> cps t.Result)
直接継続できる
アジェンダ
ユーザーインターフェイスとスレッド親和性
UIキューの隠蔽
非同期処理との融合
.NET Task (.NET 4.0 / C# 4.0)
まずは、.NET 4.0 TaskのCPS (ContinueWithを使う)
関数に直接継続を渡していないが、
Task.ContinueWithに継続を渡している ≒ CPS
注: 手を抜いてasync使ってます
.NET Task (.NET 4.5/C# 5.0)
そして、.NET 4.5/C# 5.0 でのasync-awaitによる非同期処理
構文糖による継続
async-awaitでもTaskを使う
.NET Task comparison
これが .NET 4.0 (C# 4.0 Task only) こうなる .NET 4.5 (C# 5.0 async-await)
F# Async workflow
ほとんどasync-awaitと一緒
返されるのは Async<int>
F# Asyncクラスも内部でSynchContextを使ってスレッド親和性
を担保します。
つまり?
UIの応答処理をワーカースレッドにオフロードしたい。
Task.ContinueWithを使い、CPSな継続処理を登録する。
登録されたCPSは、SynchContextを使ってスレッド親和性を担
保する(正しいスレッドでCPSを実行する)。
上記インフラはTaskクラスで実現しているので、C#のasync-
awaitでも機能する
F# Async workflowでもAsyncクラスがSynchContextを使うので、
全く同じように機能する。
つまり?
TaskやAsyncは、ワーカースレッドだけではなく、I/O非同期処
理を表すこともできる(むしろそっちが主体)。
これらを全て統一して扱い、なおかつスレッド
親和性を担保するために、.NETの背景でCPSが継
続しまくってますよ!!
ご清聴ありがとうございました!
スライドはブログに上げます
◦ http://www.kekyo.net/

Thread affinity and CPS