時間のかかる処理をする場合、メインのスレッドで実行してしまうとボタンクリックなどの画面操作が出来なくなってしまいます。そんな時は処理を別スレッドで行いメインスレッドとは非同期で動作させるのが望ましいです。
そんな非同期の処理についてC#ではasync/awaitという便利な構文があります。
詳細はこちら↓
ですが、スレッドを分けた場合、他のスレッドで動作するコントロールを直接操作する事は出来ません。
処理の進捗具合をTextBoxに表示させる場合など、表示に利用したいコントロールが別スレッドである事を意識してコーディングする必要があります。
ダメな例
以下はWPFをつかって処理の進捗をTextBoxへ表示しようとしている例。
<Window x:Class="WpfTest1.MainWindow" Title="MainWindow" Height="150" Width="250"> <Grid Margin="10"> <StackPanel> <TextBlock x:Name="CtrlText" Text="進捗:"/> </StackPanel> </Grid> </Window>
public partial class MainWindow : Window { // 時間のかかる処理をするメソッド private async void Test() { await Task.Run(() => { for (int i = 1; i <= 100; ++i) { // // ここで重たい処理をする // // CtrlText.Text = "進捗:" + i.ToString() + "%"; } }); } public MainWindow() { InitializeComponent(); Test(); } }
6行目でawaitが定義され、Task化された8行目~15行目は非同期で実行されます。
14行目で進捗を表示するためにTextBoxのTextプロパティを書き換えようとしていますが・・・
実行してみると、14行目でSystem.InvalidOperationExceptionの例外が発生してしまいます。
「このオブジェクトは別のスレッドに所有されているため、呼び出しスレッドはこのオブジェクトにアクセスできません。」という内容
これは、awaitにより別スレッドで実行されている処理からはメインスレッドが所有するTextBoxコントロールにはアクセスできない為です。
正しい例
public partial class MainWindow : Window { // 時間のかかる処理をするメソッド private async void Test() { await Task.Run(() => { for (int i = 1; i <= 100; ++i) { // // ここで重たい処理をする // // this.Dispatcher.Invoke((Action)(() => { CtrlText.Text = "進捗:" + i.ToString() + "%"; })); } }); } public MainWindow() { InitializeComponent(); Test(); } }
14行目でthis.Dispatcher.Invokeメソッドを呼び出しています。
Invokeメソッドへ渡す引数はDelegate(Actionというパラメータなし戻り値なしのメソッド)です。
14行目~17行目ではラムダ式(=>)を使って TextBoxのTextプロパティを書き換えるメソッドを作成しています。
Dispatcherはキューを管理するクラスです。
this は MainWindow オブジェクト(つまりTextBox管理しているウィンドウ)ですね。
つまり、TextBoxを所有しているスレッドのキュー管理(this.Dispatcher)に対して、このデリゲートを実行してねっと依頼しているイメージになります。
関連記事
- C#の値型と参照型の違い
- C#のコンストラクタでオーバーロード
- C#のコンストラクタの継承
- C#のジェネリックを使おう
- C#のデリゲート (delegate) って何?
- C#のデリゲートお手軽にする匿名メソッド
- C#のラムダ式【=>】って何?
- C#で基底クラスのメソッドを置き換えるオーバーライド
- C#でキャストとas演算子を使いこなす
- C#で型を判別するtypeofとis演算子
- C#の値型でもnullを扱えるようにするNullable
- C#のリソース解放にはIDisposableとusingを使おう
- C#のStringとstring、Int32とint 違いは・・・ない!
- C#でasync/awaitを使った非同期処理
- C#で文字列を指定の区切り文字で分割
- C#のstring.Formatで桁数や書式を指定する
- C#の配列やListをソートする
- C#の配列やListを検索する (Find,FindAll,FindIndex)
- C#の配列やListを高速に検索する (BinarySearch)
- C#の配列の中に指定の要素が存在するかを調べる(LINQ Contains)
- C#の配列の中に条件を満たす要素が存在するかを調べる(LINQ Any)
- C#の配列から条件に合う要素を抽出する(LINQ Where)
- C#の配列で要素毎の処理結果を得る(LINQ Select)
- C#の配列を並び替える(LINQ OrderBy,ThenBy)
- C#の配列をグループ毎に処理する(LINQ GroupBy)
- C#の配列を内部結合(INNER JOIN)する(LINQ Join)
- C#の配列から最初の要素を取り出す(LINQ First,FirstOrDefault)
- C#の配列の重複要素を削除する(LINQ Distinct)
- C#でフォルダ内のファイル名一覧を取得する
- C#でテキストファイルを読み込む
- C#でテキストファイルに書き込む
- C#でバイナリファイルを読み込む
- C#でバイナリファイルに書き込む
コメントをお書きください