WinodwsFormsアプリから学ぶ同期と非同期の違い

同期プログラミングと非同期プログラミングの違いを示すWindows Formsのサンプルを紹介します

同期と非同期

同期(Synchronous)とは、1つの処理が終了するまで、次の処理を開始することを待つことを意味します。同期処理では、処理中に他の処理を行えません。

非同期(Asynchronous)とは、1つの処理が終了するまで、次の処理を開始することを待たずに、他の処理を行えることを意味します。非同期処理は、同時に複数のタスクを実行することができ、ユーザーインターフェイスをフリーズさせないために使用されます。

次の例では、「同期実行ボタン」を押すと「重い処理」が同期的に実行されます。これにより、「重い処理」の実行中に他の処理を行えなくなり、ユーザーインターフェイスもフリーズします。一方、「非同期実行ボタン」を押すと「重い処理」が非同期的に実行されます。これにより、「重い処理」の実行中に他の処理を行えます。

テストアプリの動作

このアプリケーションは、「同期実行ボタン」と「非同期実行ボタン」、「色変更ボタン」の3つのボタンを持っています。

Form1クラスは、Formクラスを継承し、各ボタンに対応するイベントハンドラを定義しています。

「同期実行ボタン」を押すと、同期処理として「重い処理」が実行されます。この場合、「重い処理」が終了するまで、ユーザーインターフェイス(UI)スレッドはブロックされます。

「非同期実行ボタン」を押すと、非同期処理として「重い処理」が実行されます。この場合、「重い処理」は、別のバックグラウンドスレッドで実行され、UIスレッドはブロックされません。「重い処理」が終了した後、結果ラベルが更新されます。

「色変更ボタン」を押すたびに、ボタンの背景色がランダムに変更されます。

このコードは、同期処理と非同期処理の使い方を示す簡単な例です。

テストアプリの作成

実行した様子

フォームデザイン

画面デザインになります

テスト用のコード

namespace AsyncAndSyncSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            // カラーチェンジボタンの文字色の初期を白に
            colorChangeButton.ForeColor = Color.White;
        }

        // 同期で処理される実行ボタンのイベントハンドラ
        private void SyncButton_Click(object sender, EventArgs e)
        {
            // 同期処理
            ResultLabel.Text = "同期処理を開始";
            PerformHeavyTask();
            ResultLabel.Text = "同期処理を終了";
        }

        // 非同期で処理される実行ボタンのイベントハンドラ
        private void AsyncButton_Click(object sender, EventArgs e)
        {
            // 非同期処理
            ResultLabel.Text = "非同期処理を開始";
            PerformHeavyTaskAsync();
        }

        // 重い処理
        private void PerformHeavyTask()
        {
            // 長時間実行されるタスクの実行(5秒間スリープします)
            Thread.Sleep(5000);
        }

        // 重い処理を非同期で実行
        private async void PerformHeavyTaskAsync()
        {
            await Task.Run(() => PerformHeavyTask());
            ResultLabel.Text = "非同期処理を終了";
        }

        // ボタンを押すたびに色が変わるイベントハンドラ
        private void colorChangeButton_Click(object sender, EventArgs e)
        {
            Random random = new Random();
            colorChangeButton.BackColor = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256));
        }
    }
}

コードの説明

Form1クラスは、WinFormsのフォームクラスを拡張しています。初期化メソッド(InitializeComponent)が呼び出され、「カラーチェンジボタン」の文字色が白に設定されます。

  • SyncButton_Clickメソッドは、同期処理ボタンのイベントハンドラです。「同期処理を開始」というメッセージが表示され、「PerformHeavyTask」メソッドが呼び出されます。「PerformHeavyTask」メソッドが終了した後、「同期処理を終了」というメッセージが表示されます。
  • AsyncButton_Clickメソッドは、非同期処理ボタンのイベントハンドラです。「非同期処理を開始」というメッセージが表示され、「PerformHeavyTaskAsync」メソッドが呼び出されます。「PerformHeavyTaskAsync」メソッドは、「PerformHeavyTask」メソッドを非同期で実行するために、Task.Runメソッドを使用します。「PerformHeavyTask」メソッドが終了した後、「非同期処理を終了」というメッセージが表示されます。
  • PerformHeavyTaskメソッドは、重いタスクを実行するためのメソッドです。この例では、5秒間スリープするというシンプルなタスクが実行されます。
  • PerformHeavyTaskAsyncメソッドは、非同期で「PerformHeavyTask」メソッドを実行するためのメソッドです。
  • colorChangeButton_Clickメソッドは、ボタンを押すたびに背景色が変わるイベントハンドラです。このメソッドは、毎回新しいランダムな色を生成して、「colorChangeButton」の背景色を設定します。

実際に有効性のあるコードにするためには改善が必要

このコードについては以下の改善点が考えられます。

  1. 非同期メソッド PerformHeavyTaskAsync の戻り値:非同期メソッドは常に Task オブジェクトを返す必要がありますが、このメソッドは戻り値がありません。このメソッドを非同期処理として使用する場合は、戻り値を Task オブジェクトに変更する必要があります。
  2. PerformHeavyTaskAsync メソッドの async 修飾子:このメソッドは非同期処理を行いますが、async 修飾子を使用することで明示的に非同期メソッドとしてマークすることができます。
  3. ResultLabel の変更:このコードでは、同期処理と非同期処理で ResultLabel のテキストを変更するという処理がありますが、複数のスレッドから同時にこのコントロールを変更することができるため、競合状態が発生する可能性があります。この問題を解決するためには、Invoke メソッドを使用するなどの方法があります。

改善したコード

namespace AsyncAndSyncSample
{
    public partial class Form1 : Form
    {
        private readonly Random _random = new Random();
        private readonly object _syncRoot = new object();

        public Form1()
        {
            InitializeComponent();
            colorChangeButton.ForeColor = Color.White;
        }

        private void SyncButton_Click(object sender, EventArgs e)
        {
            ResultLabel.Text = "同期処理を開始";
            PerformHeavyTask();
            ResultLabel.Text = "同期処理を終了";
        }

        private async void AsyncButton_Click(object sender, EventArgs e)
        {
            ResultLabel.Text = "非同期処理を開始";
            await PerformHeavyTaskAsync();
            ResultLabel.Text = "非同期処理を終了";
        }

        private void PerformHeavyTask()
        {
            Thread.Sleep(5000);
        }

        private Task PerformHeavyTaskAsync()
        {
            return Task.Run(() => PerformHeavyTask());
        }

        private void ColorChangeButton_Click(object sender, EventArgs e)
        {
            lock (_syncRoot)
            {
                colorChangeButton.BackColor = Color.FromArgb(_random.Next(256), _random.Next(256), _random.Next(256));
            }
        }
    }
}

改善したコード(コメント付き)

// Form1クラスの拡張部分の定義
namespace AsyncAndSyncSample
{
    // Form1クラスの定義
    public partial class Form1 : Form
    {
        // 乱数生成用の変数
        private readonly Random _random = new Random();
        // 同期用のオブジェクト
        private readonly object _syncRoot = new object();

        // コンストラクタ
        public Form1()
        {
            // 初期化メソッド呼び出し
            InitializeComponent();
            // colorChangeButtonのフォントカラーを白に設定
            colorChangeButton.ForeColor = Color.White;
        }

        // SyncButtonのクリックイベントハンドラ
        private void SyncButton_Click(object sender, EventArgs e)
        {
            // ResultLabelに「同期処理を開始」というテキストを設定
            ResultLabel.Text = "同期処理を開始";
            // 重いタスクを実行
            PerformHeavyTask();
            // ResultLabelに「同期処理を終了」というテキストを設定
            ResultLabel.Text = "同期処理を終了";
        }

        // AsyncButtonのクリックイベントハンドラ
        private async void AsyncButton_Click(object sender, EventArgs e)
        {
            // ResultLabelに「非同期処理を開始」というテキストを設定
            ResultLabel.Text = "非同期処理を開始";
            // 非同期で重いタスクを実行
            await PerformHeavyTaskAsync();
            // ResultLabelに「非同期処理を終了」というテキストを設定
            ResultLabel.Text = "非同期処理を終了";
        }

        // 重いタスクを実行するメソッド
        private void PerformHeavyTask()
        {
            // 5秒待機
            Thread.Sleep(5000);
        }

        // 非同期で重いタスクを実行するメソッド
        private Task PerformHeavyTaskAsync()
        {
            // PerformHeavyTaskメソッドをタスクとして実行して返す
            return Task.Run(() => PerformHeavyTask());
        }

        // ColorChangeButtonのクリックイベントハンドラ
        private void ColorChangeButton_Click(object sender, EventArgs e)
        {
            // 同期用のオブジェクトを使ってロック
            lock (_syncRoot)
            {
                // colorChangeButtonの背景色を設定
                colorChangeButton.BackColor = Color.FromArgb(_random.Next(256), _random.Next(256), _random.Next(256));
            }
        }
    }
}

改善点:

  • PerformHeavyTaskAsync メソッドから返されるタスクを非同期に実行するために await キーワードを使用するように更新しました。
  • ColorChangeButton_Click メソッド内で同時実行が可能な問題を回避するために、 lock ステートメントを使用しました。
  • ボタンのクリックハンドラー内での ResultLabel.Text の更新を、正しい位置に移動しました。
  • 全体的にコードの構造をより読みやすく整理しました。