「C Sharpの基礎 - マルチスレッドとGUI」の版間の差分

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

Wiki がページ「マルチスレッドにおけるGUIコントロールの操作」を「マルチスレッドにおけるGUIコントロールの操作(C Sharp)」に、リダイレクトを残さずに移動しました
 
(同じ利用者による、間の8版が非表示)
13行目: 13行目:
GUIスレッドに割り当てられたデフォルトのタスクスケジューラを取得する。<br>
GUIスレッドに割り当てられたデフォルトのタスクスケジューラを取得する。<br>
GUIコントロール(この場合はテキストボックス)にアクセスするところでGUIスレッド上で動作するように指定する。<br>
GUIコントロール(この場合はテキストボックス)にアクセスするところでGUIスレッド上で動作するように指定する。<br>
  <source lang="c#">
  <syntaxhighlight lang="c#">
  private async void buttonl_Click(object sender, RoutedEventArgs e)
  private async void buttonl_Click(object sender, RoutedEventArgs e)
  {
  {
33行目: 33行目:
     }, taskScheduler); // UI スレッド上で動作することを指定する
     }, taskScheduler); // UI スレッド上で動作することを指定する
  }
  }
  </source>
  </syntaxhighlight>
<br><br>
<br><br>


== Invokeメソッドを使う ==
== Invokeメソッドを使う ==
Windows フォームアプリケーションの場合、GUIコントロールのInvokeメソッドを呼び出すと、<br>
コントロールの<code>Invoke</code>メソッドを使用する場合、呼び出し元のオブジェクトのスレッドにおいてデリゲートを実行することができる。<br>
呼び出し元のオブジェクトのスレッドにおいてデリゲートを実行することができる。<br>
  <syntaxhighlight lang="c#">
実行結果は、前項とほぼ同じなので省略する。<br>
  <source lang="cpp">
  private async void buttonl_Click(object sender, RoutedEventArgs e)
  private async void buttonl_Click(object sender, RoutedEventArgs e)
  {
  {
55行目: 53行目:
     });
     });
  }
  }
  </source>
  </syntaxhighlight>
<br>
また、コントロールの<code>Invoke</code>メソッドと<code>InvokeRequired</code>プロパティを組み合わせて使用することもできる。<br>
* <code>Invoke</code>メソッド
*: 指定された処理をそのコントロールが生成されたスレッドで実行する。
*: 多少オーバーヘッドが発生する。
* <code>InvokeRequired</code>プロパティ
*: コントロールにアクセスする時、<code>Invoke</code>メソッドを使用する必要があるかどうかを判断する。
<syntaxhighlight lang="c#">
// 必要に応じてInvokeするコード
public partial class Form1 : Form
{
    // ...略
    private async void buttonl_Click(object sender, RoutedEventArgs e)
    {
      InvokeIfRequired(button1, () => button1.Enabled = false);
      InvokeIfRequired(button1, () => button1.Enabled = true);
    }
    private void InvokeIfRequired(Control control, Action action)
    {
      if (control.InvokeRequired)
      {
          control.Invoke(action, new object[] { });
      }
      else
      {
          action();
      }
    }
}
</syntaxhighlight>
<br>
また、以下のようなコントロールに対する拡張メソッドとして定義することにより、簡潔に記述できるようになる。<br>
<syntaxhighlight lang="c#">
public partial class Form1 : Form
{
    // ...略
    private async void buttonl_Click(object sender, RoutedEventArgs e)
    {
      button1.InvokeIfRequired(() => button1.Enabled = false);
      button1.InvokeIfRequired(() => button1.Enabled = true);
    }
    // ...略
}
// コントロールに対する拡張メソッド
public static class ControlExtensions
{
    // 戻り値が不要な場合
    public static void InvokeIfRequired(this Control control, Action action)
    {
      if (control.InvokeRequired)
      {
          control.Invoke(action, new object[] { });
      }
      else
      {
          action();
      }
    }
    // 戻り値が必要な場合
    public static T InvokeIfRequired<T>(this Control control, Func<T> func)
    {
      if (control.InvokeRequired)
      {
          return (T)control.Invoke(func, new object[] { });
      }
      else
      {
          return func();
      }
    }
}
</syntaxhighlight>
<br><br>
<br><br>


64行目: 140行目:
GUIスレッドに割り当てられたデフォルトのタスクスクジューラを取得して、タスクを作成するときのオプション設定に使用する。<br>
GUIスレッドに割り当てられたデフォルトのタスクスクジューラを取得して、タスクを作成するときのオプション設定に使用する。<br>
また、進捗の対象とするループ内の子タスクとしてプログレスバーを進捗させるタスクを設定する。<br>
また、進捗の対象とするループ内の子タスクとしてプログレスバーを進捗させるタスクを設定する。<br>
  <source lang="cpp">
  <syntaxhighlight lang="cpp">
  private void button1_Click(object sender, RoutedEventArgs e)
  private void button1_Click(object sender, RoutedEventArgs e)
  {
  {
96行目: 172行目:
     }, taskScheduler);
     }, taskScheduler);
  }
  }
  </source>
  </syntaxhighlight>
<br><br>
<br><br>


== ボタンを押下した時に処理を中止する ==
== マルチスレッドのキャンセル処理 ==
  <source lang="cpp">
  <syntaxhighlight lang="cpp">
  private void btnStart_Click(object sender, RoutedEventArgs e)
  private void btnStart_Click(object sender, RoutedEventArgs e)
  {
  {
168行目: 244行目:
  private void btnCancel_Click(object sender, RoutedEventArgs e)
  private void btnCancel_Click(object sender, RoutedEventArgs e)
  {
  {
    if (source == null)
    {
      return;
    }
     source.Cancel();
     source.Cancel();
  }
  }
  </source>
  </syntaxhighlight>
<br>
以下の例では、DoWorkメソッドの引数に<code>CancellationToken</code>構造体を指定して、<br>
メソッド内で<code>IsCancellationRequested</code>プロパティまたは<code>ThrowIfCancellationRequested</code>メソッドを使用して、キャンセル処理をしている。<br>
<br>
<u><code>ThrowIfCancellationRequested</code>メソッドを使用してキャンセルする場合、非同期メソッドの<code>Task</code>の状態が<code>Canceled</code>の状態になるため、キャンセルされたということが明確になる。(推奨)</u><br>
<br>
<code>await</code>処理のメソッド内でキャンセルする場合、<code>await</code>処理のメソッド内にも再帰的に<code>CancellationToken</code>構造体を渡す。<br>
各非同期メソッドに渡す<code>CancellationToken</code>構造体は、基本的に<code>CancellationTokenSource</code>クラスから生成する。<br>
<code>CancellationTokenSource</code>クラスの<code>Cancel</code>メソッドを呼ぶことにより、<code>CancellationToken</code>オブジェクトが<code>Canceled</code>の状態になる。<br>
<br>
一般的な非同期ライブラリのAPIであれば、時間を要する可能性がある処理には<code>CancellationToken</code>構造体を受け入れるオーバーロードが存在する。<br>
<syntaxhighlight lang="c#">
private CancellationTokenSource cts = null;
private async void button_Click(object sender, EventArgs e)
{
    cts = new CancellationTokenSource();  // CancellationTokenSourceクラスのインスタンスを生成
    int intValue = await DoWork(this.cts.Token);  // CancellationTokenSourceオブジェクトからCancellationTokenを取り出して渡す
}
private async Task<int> DoWork(CancellationToken token)
{
    await DoHeavyWork(token);  // 時間の掛かる処理にもCancellationToken構造体を渡す
    // キャンセル処理 (IsCancellationRequestedプロパティを使用する場合)
    if (token.IsCancellationRequested)
    {
      return 1;
    }
    // キャンセル処理 (ThrowIfCancellationRequestedメソッドを使用する場合)
    // これは、キャンセルが要求されている場合、例外OperationCanceledExceptionを発生させる
    //token.ThrowIfCancellationRequested();
    return 0;
}
 
// キャンセルボタン押下
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
    if (this.cts == null)
    {
      return;
    }
    this.cts.Cancel();  // Cancelメソッドを実行した時、非同期メソッドであるDoWorkメソッドがキャンセルされる
}
</syntaxhighlight>
<br><br>
 
== マルチスレッドとプログレスバー ==
マルチスレッドの実行は、<code>async</code>および<code>await</code>を使用して簡潔に記述できる。<br>
ただし、マルチスレッド処理ではコントロール(ユーザインターフェイス)を直接操作できない。(例外が発生する)<br>
<br>
マルチスレッド内でプログレスバー(<code>Progress</code>クラス)を制御する場合、まず、<code>Progress</code>オブジェクトに進捗を表示するためのメソッドを登録する。<br>
マルチスレッドのメソッドの引数には、<code>IProgress</code>インターフェース(<code>System</code>名前空間)を追加する。<br>
<br>
マルチスレッド処理内で<code>Progress</code>オブジェクトの<code>Report</code>メソッドを呼ぶと、<code>Progress</code>オブジェクトに登録されているメソッドがUIスレッドで実行される。<br>
<br>
また、マルチスレッド処理内でのキャンセル処理も進捗表示と似た方法で行う。<br>
なお、キャンセル処理は<code>CancellationToken</code>構造体(<code>System.Threading</code>名前空間)を使用するが、上記の進捗表示より複雑になることに注意する。<br>
<syntaxhighlight lang="c#">
// 方法 1
private async void button2_Click(object sender, EventArgs e)
{
    DisableAllButtons();
    toolStripStatusLabel1.Text = "処理中…";
    toolStripProgressBar1.Value = 0;
 
    // Progressクラスのインスタンスを生成
    var p = new Progress<int>(ShowProgress);
    // 時間のかかる処理を別スレッドで開始
    string result = await Task.Run(() => DoWork(p, 100));
    // 処理結果の表示
    toolStripStatusLabel1.Text = result;
    toolStripProgressBar1.Value = 100;
    MessageBox.Show("正常に完了");
    EableAllButtons();
}
// 進捗を表示するメソッド(UIスレッドで呼び出される)
private void ShowProgress(int percent)
{
    toolStripStatusLabel1.Text = percent + "% 完了";
    toolStripProgressBar1.Value = percent;
}
// 時間のかかる処理を行うメソッド(進捗付き)
private string DoWork(IProgress<int> progress, int n)
{
    // 別スレッドで実行されるため、このメソッドではUI(コントロール)を操作してはいけない
    // 時間のかかる処理
    for (int i = 1; i <= n; i++)
    {
      System.Threading.Thread.Sleep(100);
      int percentage = i * 100 / n; // 進捗率
      progress.Report(percentage);
    }
    return "全て完了";
}
private void DisableAllButtons()
{
    button1.Enabled = false;
    button2.Enabled = false;
}
 
private void EableAllButtons()
{
    button1.Enabled = true;
    button2.Enabled = true;
}
</syntaxhighlight>
<br>
<syntaxhighlight lang="c#">
// 方法 2 (Invokeメソッドの使用)
private async void button2_Click(object sender, EventArgs e)
{
    DisableAllButtons();
    toolStripStatusLabel1.Text = "処理中…";
    toolStripProgressBar1.Value = 0;
 
    // 時間のかかる処理を別スレッドで開始
    string result = await DoWorkAsync(100);
    // 処理結果の表示
    toolStripStatusLabel1.Text = result;
    toolStripProgressBar1.Value = 100;
    MessageBox.Show("正常に完了");
    EableAllButtons();
}
// 時間のかかる処理を行うメソッド(進捗付き)
private async Task<string> DoWorkAsync(int n)
{
    // 時間のかかる処理
    for (int i = 1; i <= n; i++)
    {
      await Task.Delay(100);
      int percentage = i * 100 / n; // 進捗率
      // 進捗メッセージの変更
      toolStripStatusLabel1.Invoke(new Action(() =>
      {
          toolStripStatusLabel1.Text  = percentage + "% 完了";
      }));
      // プログレスバーの進捗の変更
      toolStripProgressBar1.Invoke(new Action(() =>
      {
          toolStripProgressBar1.Value = percentage;
      }));
    }
    return "全て完了";
}
private void DisableAllButtons()
{
    button1.Enabled = false;
    button2.Enabled = false;
}
 
private void EableAllButtons()
{
    button1.Enabled = true;
    button2.Enabled = true;
}
</syntaxhighlight>
<br><br>
<br><br>


__FORCETOC__
__FORCETOC__
[[カテゴリ:C_Sharp]]
[[カテゴリ:C_Sharp]]