MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C Sharpの基礎 - マルチスレッドとGUIのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C Sharpの基礎 - マルチスレッドとGUI
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == マルチスレッドにおいて、GUIコントロールを操作することは重要なテーマの1つであり、操作しなければならないケースは次の2つだと考えられる。<br> * 処理結果をGUIコントロールに反映する * 処理を中止する <br> 処理結果をGUIコントロールに反映するとは、結果をテキストボックスに表示する、または、処理の進捗をプログレスバーに表示する等である。<br> 処理を中止するとは、処理対象のデータに不都合があって処理が不要になる、または、予想より処理時間が長くなり途中で中止する等である。<br> この場合、キャンセルボタンを押下することで中止の意図をアプリケーションに通知することになる。<br><br> == 処理結果をテキストボックスに表示する == 下記のサンプルコードは、ボタンを押下するとTask.Runで並列処理した結果をテキストボックスに表示するプログラムの例である。<br> ポイントは、TaskSchedulerクラスの静的なFromCurrentSynchronizationContextメソッドを呼び出して、<br> GUIスレッドに割り当てられたデフォルトのタスクスケジューラを取得する。<br> GUIコントロール(この場合はテキストボックス)にアクセスするところでGUIスレッド上で動作するように指定する。<br> <syntaxhighlight lang="c#"> private async void buttonl_Click(object sender, RoutedEventArgs e) { // UI スレッドのデフォルトのタスクスケジューラ var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); textBox.Text = String.Format("UI ThreadId= {0}\r\n", Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { string s = String.Format("index= {0}, ThreadId= {1}\r\n", i, Thread.CurrentThread.ManagedThreadId); return s; // タスクの戻り値 }) .ContinueWith(t => { textBox.Text += t.Result + "\r\n"; textBox.Text += String.Format("UI ThreadId= {0}\r\n", Thread.CurrentThread.ManagedThreadId); }, taskScheduler); // UI スレッド上で動作することを指定する } </syntaxhighlight> <br><br> == Invokeメソッドを使う == コントロールの<code>Invoke</code>メソッドを使用する場合、呼び出し元のオブジェクトのスレッドにおいてデリゲートを実行することができる。<br> <syntaxhighlight lang="c#"> private async void buttonl_Click(object sender, RoutedEventArgs e) { textBox.Text = String.Format("UI ThreadId= {0}\r\n", Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { string s = String.Format("index= {0}, ThreadId= {1}\r\n", i, Thread.CurrentThread.ManagedThreadId); textBox.Invoke(new Action(() => // ← この行に注目 { textBox.Text += s; })); }); } </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> ポイントは、TaskSchedulerクラスのFromCurrentSynchronizationContextメソッドを呼び出して、<br> GUIスレッドに割り当てられたデフォルトのタスクスクジューラを取得して、タスクを作成するときのオプション設定に使用する。<br> また、進捗の対象とするループ内の子タスクとしてプログレスバーを進捗させるタスクを設定する。<br> <syntaxhighlight lang="cpp"> private void button1_Click(object sender, RoutedEventArgs e) { // UIスレッドへのスケジュール用 var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // プログレスバーの設定 progressBar.Visibility = Visibility.Visible; progressBar.Minimum = 0; progressBar.Maximum = 100; progressBar.Value = 0; var task = Task.Run(() => { Parallel.For(0, 100, i => { // プログレスバーを進捗させるためのタスクをForループ内におく Task.Run(() => { progressBar.Value += 1; System.Windows.Forms.Application.DoEvents(); // プログレスバーの進捗を表示するため }, CancellationToken.None, TaskCreationOptions.None, taskScheduler); }); }); // 継続元のタスクが終了したあとの処理 var continueTask = task.ContinueWith(t => { progressBar.Visibility = Visibility.Hidden; progressBar.Value = 0; }, taskScheduler); } </syntaxhighlight> <br><br> == マルチスレッドのキャンセル処理 == <syntaxhighlight lang="cpp"> private void btnStart_Click(object sender, RoutedEventArgs e) { source = new CancellationTokenSource(); CancellationToken token = source.Token; // タスクをキャンセルしたときのアクションを登録 token.Register(() => { progressBar.Visibility = Visibility.Hidden; progressBar.Value = 0; btnCancel.Enabled = false; textBox.Text = "キャンセルされました"; }); // UIスレッドへのスケジュール用 var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); btnCancel.Enabled = true; // プログレスバー設定 progressBar.Visibility = Visibility.Visible; progressBar.Minimum = 0; progressBar.Maximum = 100; progressBar.Value = 0; var task = Task.Run(() => { // キャンセルが要求されていたら、OperationCanceledException例外を発生させる token.ThrowIfCancellationRequested(); Parallel.For(0, 100, (n) => { Thread.Sleep(200); // n を使う時間のかかる処理のかわり // プログレスバーを進捗させるためのタスクを For ループ内におく var progressTask = Task.Run(() => { progressBar.Value += 1; System.Windows.Forms.Application.DoEvents(); // プログレスバーの進捗を表示するため }, token, TaskCreationOptions.None, taskScheduler); try { await progressTask; // progressTaskの実行終了を待つ } catch (AggregateException ae) { ae.Handle((x) => { return true; }); } }); }); // 継続元のタスクが正常に終了したあとの処理 var continueTask = task.ContinueWith(t => { progressBar.Visibility = Visibility.Hidden; progressBar.Value = 0; btnCancel.Enabled = false; textBox.Text = "正常終了しました。"; }, taskScheduler); } private void btnCancel_Click(object sender, RoutedEventArgs e) { if (source == null) { return; } source.Cancel(); } </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> __FORCETOC__ [[カテゴリ:C_Sharp]]
C Sharpの基礎 - マルチスレッドとGUI
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse