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」の背景色を設定します。
実際に有効性のあるコードにするためには改善が必要
このコードについては以下の改善点が考えられます。
- 非同期メソッド
PerformHeavyTaskAsync
の戻り値:非同期メソッドは常にTask
オブジェクトを返す必要がありますが、このメソッドは戻り値がありません。このメソッドを非同期処理として使用する場合は、戻り値をTask
オブジェクトに変更する必要があります。 PerformHeavyTaskAsync
メソッドのasync
修飾子:このメソッドは非同期処理を行いますが、async
修飾子を使用することで明示的に非同期メソッドとしてマークすることができます。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
の更新を、正しい位置に移動しました。 - 全体的にコードの構造をより読みやすく整理しました。
ディスカッション
コメント一覧
まだ、コメントがありません