【WPF】UIからスレッドを起動し、結果をUIで受け取るには(SQLiteの例とソースコード付き)

当ページのリンクには広告が含まれています。

WPFアプリケーションでデータベース検索を行う際、検索処理が長引くと画面がフリーズしてしまうことがあります。

ユーザーの操作性を考えるなら、UIとは別のスレッドで検索処理を行い、その結果を再びUIに反映させたいところですが、単純に別スレッドからUIを更新するだけでは、エラーになってしまいます。

本記事では、「データやファイルの読み込み中であっても、画面がフリーズすることなく操作ができるようにしたい」という目標を達成するため、別スレッドからUIを操作する方法を紹介します。

スレッドの扱いが初めての方でも実装できるよう、SQLiteを使った具体的な例とサンプルプログラムを交えて分かり易く解説します。

目次

解決すべき課題

UIから別スレッドを起動して、そのスレッドの処理結果を再びUIにセットすると、上記のエラーが発生します。
これは、別スレッドの中から画面のコントロールにアクセスできない」という制限があるためです。

解決方法

UIの中から別スレッドを呼び出し、スレッドの処理が完了した時点で、 Dispatcher.Invoke を使ってUIに結果を返すことで解決できます。

下記は、非同期に対応していない ReadAllLines を別タスクで実行し、読み込んだテキストを MyTextBox という名前のUIコントロールにセットするサンプルです。

ReadAllLines の非同期版として ReadAllLinesAsync が用意されており、こちらを使う方がパフォーマンスや安全性は高まりますが、ちょっとした非同期処理であれば ReadAllLines でも問題ありません。
ちなみに、正しく非同期にしたい場合は次の様に書きます。

Task.Run(async () =>
var lines = await File.ReadAllLinesAsync(filename);
・・・以下省略

別タスクの中でイベントを発火させUIを更新するには

先ほどのサンプルでは、別スレッドの処理が終わったタイミングで Dispatcher.Invoke を呼び出していましたが、処理の最後にイベントを発行させてUIを更新する方法もあります。

この方法は、呼び出されるクラスにイベントが用意されていることが前提ですが、うまく使えば処理の進捗状況を逐次UIに反映させる場合に便利です。ちなみに、後ほど紹介するサンプルプログラムでは、この方法を使っています。

画面側は次の様に記述します。

Dispatcher.Invokeの呼び出す時の2つの方法

これまでの説明では、Application.Current.Dispatcher.Invoke() という呼び出し方を使っていましたが、this を使って少しだけ省略することができます。

// 記述方法1
Application.Current.Dispatcher.Invoke()
//記述方法2
this.Dispatcher.Invoke()

Application.Current.Dispatcher は、アプリケーション全体の UI スレッドにアクセスできるため、どのウィンドウやコントロールからでも使えます(別のウィンドウにある UI にもアクセスできます)。
一方、this.Dispatcher はそのウィンドウやコントロールの UI だけにしかアクセスできません。

つまり、Application.Current はアプリ全体に通じていて、他のウィンドウにもアクセス可能ですが、this は自分自身の画面にしかアクセスできないところが違います。

後程紹介するサンプルプログラムでは、 this を使っています。

別スレッドからコントロールにアクセスする

ただし注意点があって、別タスクの検索結果をそのままコントロールにセットすると、下記のエラーが発生します。

これは、別スレッドの中から画面のコントロールにアクセスできない」という制限があるためです。

イベントハンドラに記述した処理は別スレッドから呼び出されるので、結果的に上記の制限に引っかかってエラーが発生します。

このエラー回避の回避策として、 this.Dispatcher.Invoke を使って次のように記述する方法があります。

サンプルプログラムでは、この方法を使って別スレッドの中から画面のコントロールに検索結果をセットしています。

サンプルプログラムのソースコード

サンプルプログラムの画面

今回紹介するサンプルプログラムは、SQLiteに対して検索を行っている間もUIが操作でき、かつ別スレッドの処理が完了したら結果を画面に表示するといった内容になります。

サンプルプログラムの構成

MainWindowに設置した「検索」ボタンをクリックすると、SQLiteLibクラスを呼び出し、SQLiteからデータを取得するようになっています。

取得したデータは、UIに結果を戻すために用意した TaskCompletedEventArgs クラスに格納し、完了イベントを発火させています。

SQLiteLibSQLiteに対してSQLの実行や結果の取得を行うクラス
TaskCompletedEventArgsSQLiteLibの検索メソッドで取得したデータ(検索結果はDataTableに格納)を
呼び出し元(MainWindow.xaml.cs)に返すためのクラス

サンプル プログラムのダウンロードURL

ソースコードはこちらからダウンロードできます。

尚、SQLiteのライブラリは含んでいませんので、ビルド&実行したい場合は、NuGetからパッケージをインストールして下さい。

インストール方法の詳細についてお知りになりたい方は、こちらの記事をどうぞ。

ソースコードの紹介

まず最初にMainWindow.xamlです。

次に、 MainWindow.xaml.cs です。

最後に、SQLiteLibとTaskCompletedEventArgs のクラスです。

まとめ

今回は、WPF の画面から別タスクでデータベースに検索を行い、その結果を画面に表示する一連の流れをご紹介しました。

今回の題材はデータベース検索ですが、この記事で紹介した方法は、CSV の読み書きやデータの入力待ちなど、時間がかかる処理においても応用できます。このような場合、画面をフリーズさせずに処理を進めることが可能です。

サンプルプログラムは必要最小限の機能しか持たせていませんので、例えば「検索」ボタンを押してから検索が終わるまでの間、ボタンの色を変えたり、処理完了後にメッセージを表示したりといった機能は追加していません。

これらの機能は、お好みに合わせてカスタマイズしてみてください。

この記事が皆様のプログラミングの一助となれば幸いです。れば幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次