WPFアプリケーションで非同期メソッドを扱う際、ConfigureAwait(false) を付けると UI 要素にアクセスできなくなるケースがよくある。次の最小構成で再現してみる。
画面定義(XAML)
<Window x:Class="Demo.AsyncAwaitWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="500">
<StackPanel>
<TextBlock x:Name="label" FontSize="40" Text="初期値" />
<Button Content="更新" Click="OnUpdateClicked" Height="60" />
</StackPanel>
</Window>
コードビハインド
public partial class AsyncAwaitWindow : Window
{
public AsyncAwaitWindow()
{
InitializeComponent();
}
private async void OnUpdateClicked(object sender, RoutedEventArgs e)
{
await Task.Delay(2000); // 2秒待機
// バックグラウンドで文字列を生成
var message = await Task.Run(() => "更新完了").ConfigureAwait(false);
label.Text = message; // ← ここで InvalidOperationException
}
}
実行して「更新」ボタンを押すと、System.InvalidOperationException: 呼び出し元のスレッドはこのオブジェクトの所有権を持っていないため、アクセスできません。 が発生する。
例外が起きる仕組み
WPF の UI 要素は DispatcherSynchronizationContext と呼ばれる特殊な SynchronizationContext を持ち、UI スレッド以外から触ると例外になる。await のデフォルト動作は「元のコンテキストに戻って継続」なので、ConfigureAwait(true)(省略時も同じ)を付ければ UI 続行スレッドで実行され、問題なく動作する。
var message = await Task.Run(() => "更新完了").ConfigureAwait(true); // 安全
ライブラリ vs アプリケーション
- ライブラリ側:UI に依存しない処理では
ConfigureAwait(false)を明示して、不要なコンテキスト切り替えを避けることでスループットが向上し、デッドロックリスクも減る。 - アプリケーション側:UI 要素を更新する場合は
ConfigureAwait(true)または省略して元のコンテキストに戻る必要がある。
ASP.NET Core との違い
ASP.NET Core では SynchronizationContext.Current が null であるため、ConfigureAwait(false) を付けても挙動が変わらない。結果として、ライブラリ内部では積極的に ConfigureAwait(false) を付けることが推奨されている。