もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
WInUI3/WPFで、ワーカースレッドの中に、メインスレッドでやってほしい処理(UIの更新など)を書きたいときはこうやる、というのを以前調べた。
それはそれでできてたのだが、最近別の書き方をしているのを見かけた。
それが、以前調べた「同期コンテキスト)」に関連するっぽい。 勉強がてら、実験してみる。
やりかた
SynchronizationContext.Send(SendOrPostCallback, Object)
と、
SynchronizationContext.Post(SendOrPostCallback, Object)
を使う。
実験コード
SynchronizationContext.Send()
は同期的に実行する(その場で実行し、終わるのを待つ)
SynchronizationContext.Post()
は、非同期的に実行する(その場では実行せず、対象のスレッドがキューを見たタイミングで実施する)
っぽい。
using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace SyncroTest; public partial class MainWindow : Window { private SynchronizationContext? Ctx { get => SynchronizationContext.Current; } private int ThId { get => Thread.CurrentThread.ManagedThreadId; } public MainWindow() => InitializeComponent(); private async void Button_Click(object sender, RoutedEventArgs e) { //現在の(UIスレッドの)コンテキストを取得 var context1 = Ctx; Debug.WriteLine($"Task.Run前:{ThId}"); await Task.Run(() => { Debug.WriteLine($"Task.Runの中:{ThId} 開始"); context1?.Send(callback, "Send"); context1?.Post(callback, "Post"); Debug.WriteLine($"Task.Runの中:{ThId} 終了"); }); Debug.WriteLine($"Task.Run後:{ThId}"); Debug.WriteLine($""); // --------------------- Debug.WriteLine($"context1 = Current? : {context1 == Ctx}"); Debug.WriteLine($""); } private void callback(object? param) { Debug.WriteLine($"{param}:{ThId}"); } }
実行結果
Task.Run前:1 Task.Runの中:8 開始 Send:1 Task.Runの中:8 終了 Post:1 Task.Run後:1 context1 = Current? : False
上に書いた通り、Sendはその場で、Postはワーカースレッドが終わってから、メインスレッドのタイミングで実行された。
実験コード②
上の実験コードの中のTask.Run
に、.ConfigureAwait(false);
を付けてみた。
変化点はココ。
await Task.Run(() => { Debug.WriteLine($"Task.Runの中:{ThId} 開始"); context1?.Send(callback, "Send"); context1?.Post(callback, "Post"); Debug.WriteLine($"Task.Runの中:{ThId} 終了"); }).ConfigureAwait(false);// ★ココが変化点
出力結果
Task.Run前:1 Task.Runの中:8 開始 Send:1 Task.Runの中:8 終了 Post:1 Task.Run後:8 context1 = Current? : False
Send、Postの処理の実行順は特に変化なし。
通常の.ConfigureAwait(false);
を付けたときの動作と同じで、
await Task.Run()
が終わった後のスレッドが、ワーカースレッドになったのみ。
実験コード③:Dispatcher(WPF)の実験
以前、ワーカースレッドのコードに、UI部品を更新するコードを書く(WPF)というのをやったのだが、Dispatcher.Invoke()
ほぼイコール SynchronizationContext.Send()
じゃないか?
また、Dispatcher.BeginInvoke()
ほぼイコール SynchronizationContext.Post()
じゃないか?と気づいた。
ほんとにそうか実験してみた。
using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; namespace SyncroTest; public partial class MainWindow : Window { Dispatcher dispatcher = Dispatcher.CurrentDispatcher; private SynchronizationContext? Ctx { get => SynchronizationContext.Current; } private int ThId { get => Thread.CurrentThread.ManagedThreadId; } public MainWindow() => InitializeComponent(); private async void Button_Click(object sender, RoutedEventArgs e) { //現在の(UIスレッドの)コンテキストを取得 var context1 = Ctx; Debug.WriteLine($"Task.Run前:{ThId}"); await Task.Run(() => { Debug.WriteLine($"Task.Runの中:{ThId} 開始"); context1?.Send(callback, "Send"); dispatcher.Invoke(() => Debug.WriteLine($"dispatcher.Invoke : {ThId}")); context1?.Post(callback, "Post"); dispatcher.BeginInvoke(() => Debug.WriteLine($"dispatcher.BeginInvoke : {ThId}")); Debug.WriteLine($"Task.Runの中:{ThId} 終了"); }); Debug.WriteLine($"Task.Run後:{ThId}"); Debug.WriteLine($""); // --------------------- Debug.WriteLine($"context1 = Current? : {context1 == Ctx}"); Debug.WriteLine($""); } private void callback(object? param) { Debug.WriteLine($"{param}:{ThId}"); } }
結果出力
Task.Run前:1 Task.Runの中:8 開始 Send:1 dispatcher.Invoke : 1 Task.Runの中:8 終了 Post:1 dispatcher.BeginInvoke : 1 Task.Run後:1 context1 = Current? : False
sendとinvoke、postとbegininvokeで同じ動きをしているように見える。やはり同じっポイ。
参考
非同期処理とディスパッチャーufcpp.wordpress.com
備考
ワーカースレッドにはSynchronizationContext
が無いので、ワーカースレッドの中ではSynchronizationContext.Current
はnullになっている。
なので、.ConfigureAwait(false);
していたTaskの後でSynchronizationContext.Current
を取ってもnullなので注意。
参考
(C#) 同期コンテキストと Task, async/await
https://qiita.com/ikorin24/items/92ea46374642a0b59011
Unityでメインスレッドに処理を戻して実行する