同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック

C#で同期メソッド-から非同期メソッドを呼び出すと、アプリケーションがフリーズする現象について紹介します。

サンプル

以下のサンプルでアプリケーションがフリーズする状態を確認します。

UI

下図のUIを作成します。
同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック:画像1

コード

以下のコードを記述します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CallAsyncMethodFromSyncMethod
{
  public partial class Form_Main : Form
  {
    public Form_Main()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      Task<int> ret = AsyncTask();
      ret.Wait();
      textBox1.Text += Convert.ToString(ret.Result);

    }

    public async Task<int> AsyncTask()
    {
      Func<int> asyncJob = () =>
      {
        // 時間のかかる処理
        int i = 0;
        for (i = 0; i < 1000000000; i++) {
        }
        return i;
      };

      int ret = await Task.Run(asyncJob);
      return ret;
    }
  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。
同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック:画像2

[button1]をクリックします。デッドロックになり操作ができなくなることが確認できます。
同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック:画像3

解説

ボタンのクリックイベントからAsyncTask()メソッドが呼び出され、AsyncTask()メソッド内の

int ret = await Task.Run(asyncJob);

で新しいタスクが実行されます。
awaitでタスクの完了まで以降の行は実行されず待機しますが、UIスレッド側では、

ret.Wait(); が呼び出され、スレッドが待ち状態(ロック状態)になります。Task側でawait Task.Run()を抜けて次の

return ret; を実行しようとするにも、UIスレッドがロックされているため実行できず、すべてのメソッドが待ち状態になるため、デッドロックになります。

対策

以下の対策があります。

  • await 後にUIスレッドでないスレッドで以降の行を処理する
  • Waitで待たない (スレッドをロックしない)

await 後にUIスレッドでないスレッドで以降の行を処理する

Wait()メソッドでUIスレッドがTaskの終了待ち状態になっているため、

int ret = await Task.Run(asyncJob);

の後の

return ret;

をUIスレッドでないスレッドで実行できれば、タスクが終了でき、UIスレッドのWait()を抜けることができ、デッドロックを防げます。await後の処理を呼び出し元のコンテキスト(スレッド)とは異なるコンテキストで呼び出すには、ConfigureAwait()メソッドを呼び出し、引数(continueOnCapturedContext)にfalseを与えます。

コード例

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CallAsyncMethodFromSyncMethod
{
  public partial class Form_Main : Form
  {
    public Form_Main()
    {
      InitializeComponent();
    }

    private void button3_Click(object sender, EventArgs e)
    {
      Task<int> ret = AsyncTask2();
      ret.Wait();
      textBox1.Text += Convert.ToString(ret.Result);
    }

    public async Task<int> AsyncTask2()
    {
      Func<int> asyncJob = () =>
      {
        // 時間のかかる処理事
        int i = 0;
        for (i = 0; i < 1000000000; i++) {
        }
        return i;
      };

      int ret = await Task.Run(asyncJob).ConfigureAwait(continueOnCapturedContext:false);
      return ret;
    }
  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。
同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック:画像4


[Button3]をクリックします。アプリケーションはフリーズせずに動作します。タスクにより計算された結果もテキストボックスに表示されます。
同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック:画像5

Waitで待たない (スレッドをロックしない)

Waitで待たずに、Task内と同じようにawait でタスクの完了を待つことで、待ち状態になることを防ぎます。awaitで待った場合はスレッドが待機状態にはなりません。
この方法を用いる場合は、ButtonのClickイベントのメソッドに asyncを記述して、非同期メソッドにする必要があります。

コード例

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CallAsyncMethodFromSyncMethod
{
  public partial class Form_Main : Form
  {
    public Form_Main()
    {
      InitializeComponent();
    }

    //async がついていることに注意
    private async void button2_Click(object sender, EventArgs e)
    {
      int ret = await AsyncTask();
      textBox1.Text += Convert.ToString(ret);

    }

    public async Task<int> AsyncTask()
    {
      Func<int> asyncJob = () =>
      {
        // 時間のかかる処理事
        int i = 0;
        for (i = 0; i < 1000000000; i++) {
        }
        return i;
      };

      int ret = await Task.Run(asyncJob);
      return ret;
    }

  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。
同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック:画像6

[Button2]をクリックします。アプリケーションはフリーズせずに動作します。タスクにより計算された結果もテキストボックスに表示されます。
同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック:画像7

AuthorPortraitAlt
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
作成日: 2014-11-20
Copyright © 1995–2025 iPentec all rights reserverd.