---
# System prepended metadata

title: C# 同期コンテキスト `SynchronizationContext` 及び `Task.ConfigureAwait(false)` の動作 2021-11-20
tags: [async, C#, await, 同期コンテキスト, Task.ConfigureAwait(false), SynchronizationContext]

---

---
lang: ja-jp
breaks: true
---
# C# 同期コンテキスト `SynchronizationContext` 及び `Task.ConfigureAwait(false)` の動作 2021-11-20

> await演算子と同期コンテキスト
> https://ufcpp.wordpress.com/2012/11/12/asyncawait%E3%81%A8%E5%90%8C%E6%99%82%E5%AE%9F%E8%A1%8C%E5%88%B6%E5%BE%A1/
> * await演算子は同期コンテキストを自動的に拾う

> Task.ConfigureAwait(Boolean) メソッド
> https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.tasks.task.configureawait
> 継続をキャプチャされた元のコンテキストにマーシャリングする場合は `true`。それ以外の場合は `false`。

```csharp=
static async void Run()
{
    Run1(); // 直前の処理
    await Run2Async();
    Run3(); // 続きの処理
}
```

以下のようなコードと同じような処理手順になる

```csharp=
static async void Run()
{
    Run1(); // 直前の処理 ※この処理は同期的に実行される。
    
    var context = SynchronizationContext.Current;
    Run2Async().ContinueWith(t =>
        {
            context.Post(
                state =>
                {
                    // 続きの処理は呼び出し元の同期コンテキストで実行される。
                    Run3(); // 続きの処理
                }, 
                null
                );
        });
}
```

## `async` メソッド内の `await` 処理以外の処理は 呼び出し元の実行環境（同期コンテキスト）次第で決まる

## GUIイベント（UIスレッド上）から `async` メソッドを呼び出した時の動作

```csharp=
HttpClient _client = new HttpClient();
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    await _client.GetAsync(_url1);
    ThreadUnsafe();
    await _client.GetAsync(_url2);
    ThreadUnsafe();
    await _client.GetAsync(_url3);
    ThreadUnsafe();
}
```
上記コードの場合、`ThreadUnsafe()` メソッドは一度メッセージキューに溜められた後に、UIスレッド上で実行される。

その為、UI上のボタンを連打した場合は、`Button_Click_1` メソッド全体（処理の全て）があたかも非同期で動作しているような感覚となる。
※実際には、`ThreadUnsafe()` メソッド以外が別スレッドで実行される。

## UIスレッド上から、`Task.Run()` 使って UIに関する処理を実行すると同期コンテキストがUIスレッドのものとは異なるためエラーとなる。

```csharp=
HttpClient _client = new HttpClient();
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    await Task.Run(async () =>
        {
            await _client.GetAsync(_url1);
            ThreadUnsafe();    // UI処理の場合はエラーとなる。
        });
}
```

## UIスレッドから複数の重たい処理を実行する場合は、途中でUIスレッドを挟まないようにする為 `ContinueWith` で処理をつなぐ。

```csharp=
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    PreWork();
    await HeavyWork1Async()
        .ContinueWith(_ => HeavyWork2());
    UpdateUI();
}
```

## UIスレッド上からの重たい処理を `Task.ConfigureAwait(false);` として実行すると、同期コンテキストが使用されないため、その後のUI処理でエラーとなる。

```csharp=
HttpClient _client = new HttpClient();
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    await _client.GetAsync(_url1).ConfigureAwait(false);
    ThreadUnsafe();    // UI処理の場合はエラーとなる。
}
```

:::info
逆に言うと、呼び出し元スレッドの同期コンテキストに依存するとデッドロックとなる可能性がある場合は、意識的に `Task.ConfigureAwait(false);` を指定して非同期処理を実行する必要がある。
:::

:::info
`SynchronizationContext.Current` が `System.Windows.Forms.WindowsFormsSynchronizationContext` になっている場合にデッドロックを引き起こすパターンで、`Task.ConfigureAwait(false);` を使用することで解決することがある。

:::

## コンソール アプリは同期コンテキストを持っていない為、スレッドプールが使用されて呼び出し元に同期されることはない

※`SynchronizationContext.Current` が `null` となる。

```csharp=
static void Main(string[] args)
{
    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}"); // 1
    Task _ = Test001_Async();
}
static async Task Test001_Async()
{
    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}"); // 1
    await Task.Delay(3000)
        .ContinueWith(task => {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}"); // 2
        });
    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}"); // 2
}
```

:::info
`Test001_Async`メソッド全体が非同期になるわけではなく、`await`以降の処理が別のスレッドとなる。
:::


## `ASP.NET`では、`AspNetSynchronizationContext` が使用され、Webリクエストに紐づいたスレッドに同期される。

###### tags: `C#` `Task.ConfigureAwait(false)` `SynchronizationContext` `同期コンテキスト` `async` `await`
