ConfigureAwaitとSynchronizationContext
SynchronizationContextを理解し、ライブラリコードでConfigureAwait(false)を使い、非同期処理でのデッドロックを回避します。
「ConfigureAwaitとSynchronizationContext」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
SynchronizationContextとは
SynchronizationContextは、await後の継続処理をどのようにスケジュールするかを制御します。UIアプリ(WPF、WinForms)では、UIの更新がUIスレッド上で行われるようにします。ASP.NET CoreにはSynchronizationContextがありません(削除されました)。これを理解することは、正しい非同期コードを記述するうえで重要です。
ConfigureAwait の仕組み
.ConfigureAwait(false)は、現在のSynchronizationContextを捕捉せず、そのコンテキスト上で再開しないようにアウェイターへ指示します。代わりに、継続処理は利用可能なスレッドプールスレッド上で実行されます。
// Default: captures context (resumes on original context)
var data = await FetchDataAsync();
UpdateUI(data); // runs on UI thread — correct for UI apps
// ConfigureAwait(false): doesn't capture context
var data2 = await FetchDataAsync().ConfigureAwait(false);
// Runs on any thread — DON'T access UI thread-only objects here典型的なデッドロックパターン
SynchronizationContextを保持したまま.Resultや.Wait()でTaskをブロックすると、典型的なデッドロックが発生します。継続処理は、継続処理を待ってブロックされているコンテキストを待つことになるためです。
// DEADLOCK in WPF/WinForms or ASP.NET classic:
string result = GetDataAsync().Result; // blocks UI thread
// GetDataAsync() tries to resume on UI thread after await
// UI thread is blocked by .Result
// DEADLOCK!
// Fix 1: await properly (never block)
string result = await GetDataAsync();
// Fix 2: use ConfigureAwait(false) in the library
public async Task<string> GetDataAsync()
=> await client.GetStringAsync(url).ConfigureAwait(false);ライブラリでの ConfigureAwait(false)
コンテキストに依存しないライブラリコードでは、すべての await で必ずConfigureAwait(false)を使用してください。これにより、呼び出し側がコンテキスト依存の環境からライブラリを使用した場合でも、デッドロックを防止できます。
// Library code — always ConfigureAwait(false)
public async Task<User?> GetUserAsync(int id)
{
var json = await client
.GetStringAsync($"/users/{id}")
.ConfigureAwait(false);
var user = JsonSerializer.Deserialize<User>(json);
return user;
}
// Application code (has context) — leave ConfigureAwait default
public async Task LoadUserProfileAsync(int id)
{
var user = await userService.GetUserAsync(id); // context captured
ProfileLabel.Text = user?.Name; // safe to update UI
}ASP.NET Core: SynchronizationContext なし
ASP.NET Coreには意図的にSynchronizationContextがありません。継続処理はスレッドプール上で実行されます。ConfigureAwait(false)は技術的には何もしませんが、ほかのフレームワークと共有するコードでは、習慣として使用するのがよいでしょう。
// In ASP.NET Core, both are equivalent:
var data = await service.GetAsync(); // fine
var data2 = await service.GetAsync().ConfigureAwait(false); // also fine
// Context-free means: no deadlock risk from .Result in ASP.NET Core
// BUT: mixing with libraries that assume a context is still risky
// BEST PRACTICE: still use ConfigureAwait(false) in library codeスレッドプールとコンテキストによるスケジューリング
コンテキストがない場合、継続処理はTaskSchedulerによってスケジュールされます。通常はThreadPoolTaskSchedulerが使用されます。UIコンテキストがある場合、継続処理はUIメッセージループにポストされます。
// Check current synchronization context:
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
?? "No context (thread pool)");
// In a WPF event handler: "DispatcherSynchronizationContext"
// In ASP.NET Core: null (no context)
// In a unit test with xUnit: "AsyncTestSyncContext"
// After ConfigureAwait(false):
await Task.Delay(1).ConfigureAwait(false);
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
?? "No context"); // null — context was abandonedループ内での ConfigureAwait
ループ内で await すると、ConfigureAwait(false)を使用しない限り、各反復の継続処理は捕捉されたコンテキスト上で実行されます。ライブラリコードでは、ループ内のすべての await に一貫して適用してください。
// Library loop: ConfigureAwait(false) on every await
public async Task ProcessBatchAsync(IEnumerable<int> ids)
{
foreach (var id in ids)
{
var item = await FetchItemAsync(id).ConfigureAwait(false);
await SaveItemAsync(item).ConfigureAwait(false);
}
}ValueTask と ConfigureAwait
ValueTaskもConfigureAwait(false)をサポートしています。Taskと同じルールに従い、一貫して適用してください。
public async ValueTask<string> GetFromCacheAsync(string key)
{
// ValueTask with ConfigureAwait(false)
var raw = await _store.GetAsync(key).ConfigureAwait(false);
return raw ?? "";
}コンテキストを回避する Task.Run
Task.Runを使用すると、CPUバウンドの処理をスレッドプールへオフロードできます。これにより、UIスレッドのようなコンテキストを保持するスレッド上では実行されません。
// WRONG in UI app: blocks UI thread
void ButtonClick(object? sender, EventArgs e)
{
var result = HeavyComputation(); // blocks UI
Label.Text = result;
}
// CORRECT: offload to thread pool
async void ButtonClick(object? sender, EventArgs e)
{
var result = await Task.Run(() => HeavyComputation());
// Continuation captured UI context: safe to update UI
Label.Text = result;
}実践例: ConfigureAwait を使用する HttpClient ライブラリ
ある本番環境向けのHTTPクライアントラッパーでは、どのようなコンテキストから呼び出されても安全に動作するよう、全体を通してConfigureAwait(false)を使用しています。
public class ApiClient
{
private readonly HttpClient _http;
private readonly JsonSerializerOptions _opts = new(JsonSerializerDefaults.Web);
public ApiClient(HttpClient http) => _http = http;
public async Task<T> GetAsync<T>(string path, CancellationToken ct = default)
{
using var response = await _http
.GetAsync(path, ct)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await response.Content
.ReadFromJsonAsync<T>(_opts, ct)
.ConfigureAwait(false)
?? throw new InvalidOperationException("Null response");
}
}確認問題
WPFまたはWinFormsアプリで、Taskに対して .Result を呼び出すとデッドロックが発生しやすいのはなぜですか。
まとめ: ConfigureAwait と SynchronizationContext
重要なポイント:
SynchronizationContext: 継続処理を特定のスレッド(例: UIスレッド)へ戻すようにスケジュールしますConfigureAwait(false): コンテキストの捕捉をやめ、スレッドプール上で継続します- ライブラリコード: デッドロックを防ぐため、必ず
ConfigureAwait(false)を使用します - UIを扱うアプリケーションコード: 既定の
awaitを使用してUIスレッドへ戻します - ASP.NET Core: SynchronizationContextはありません。ただし、共有ライブラリでは使用するのがよい習慣です
- コンテキスト依存のアプリでは、
.Result/.Wait()でTaskを決してブロックしないでください
よくある質問
「ConfigureAwaitとSynchronizationContext」レッスンは無料ですか?
はい。「ConfigureAwaitとSynchronizationContext」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「ConfigureAwaitとSynchronizationContext」で何を学びますか?
SynchronizationContextを理解し、ライブラリコードでConfigureAwait(false)を使い、非同期処理でのデッドロックを回避します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「ConfigureAwaitとSynchronizationContext」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- IAsyncEnumerableとawait foreach
- System.Threading.Channels
- ValueTaskとアロケーションの回避
- ConfigureAwaitとSynchronizationContext