時間のかかる処理中に
- 進捗率を表示
- キャンセルボタンで処理中止
いつもどおりよくわかっていないけどできた!のでメモしとく
進捗率表示更新は
WPF/Windowsフォーム:時間のかかる処理をバックグラウンドで実行するには?(async/await編)[C#/VB]:.NET TIPS - @IT
http://www.atmarkit.co.jp/ait/articles/1512/02/news019.html
こちらからのコピペなので問題ないはず
キャンセルの処理はあちこち見ながら自分で書いたので間違っているかも
でも期待どおりの動きなのでOK
デザイン画面
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Threading;
namespace _20180503_時間のかかる処理を途中でキャンセル
{
public partial class MainWindow : Window
{
CancellationTokenSource cancelTokensource;//キャンセル判定用
public MainWindow()
{
InitializeComponent();
MyButton実行.Click += MyButton実行_Click;
Mybuttonキャンセル.Click += Mybuttonキャンセル_Click;
}
private void Mybuttonキャンセル_Click(object sender, RoutedEventArgs e)
{
if (cancelTokensource != null)
{
cancelTokensource.Cancel();//キャンセルを発行
}
}
//非同期メソッドにするのでvoidの前にasyncを付けている
private async void MyButton実行_Click(object sender, RoutedEventArgs e)
{
MyButton実行.IsEnabled = false;
MyStatusText.Text = "処理中…";
MyProgressBar.Value = 0;
//キャンセル用トークン作成
cancelTokensource = new CancellationTokenSource();
var cToken = cancelTokensource.Token;
//Progress作成時に表示更新用のメソッドを指定する
//これを時間のかかる処理に渡す
var p = new Progress<int>(ShowProgress);
//非同期で時間のかかる処理を実行、これが別スレッドで実行される
bool result = await Task.Run(() => DoWork時間かかるYO(p, cToken));
if (result == false)
{
MyStatusText.Text = "キャンセルされた";
}
else
{
MyStatusText.Text = "処理完了!";
}
MyButton実行.IsEnabled = true;
}
//5秒かかる処理
//ProgressはIProgressで受け取る
private bool DoWork時間かかるYO(IProgress<int> p, CancellationToken cancelToken)
{
for (int i = 1; i <= 50; ++i)//0.1秒の50回ループ、合計5秒
{
//キャンセル判定
if (cancelToken.IsCancellationRequested == true)
{
return false;
}
Thread.Sleep(100);//0.1秒待機
int percentage = i * 100 / 50;//進捗率
p.Report(percentage);//状況の報告
}
return true;
}
//表示更新用
private void ShowProgress(int percent)
{
MyProgressBar.Value = percent;
MyTextBlock.Text = percent.ToString() + "%完了";
}
}
}
時間のかかる処理は別スレッドで実行する
それにはasyncとawait、System.ThreadingクラスのTask.Runを使う
これで処理中にアプリが固まることがなくなるので
キャンセルボタンを押すことができるようになる
表示更新、Progressクラスを使う
別スレッドから直接UIスレッドの表示を更新することはできないので
表示更新用のメソッドを持たせたProgressオブジェクトを作成して
これを時間のかかる処理メソッドに渡す
受け取る側はProgressじゃなくてIProgressで受け取る
処理中にProgressのReportメソッドを実行して表示更新する
処理のキャンセル
CancellationTokenSourceクラスのTokenを使う
これを時間のかかる処理に渡しておく
キャンセルボタンが押されたらキャンセルを発行
時間のかかる処理中にはキャンセルが発行されたかどうかの判定をする
発行されたら停止処理
UI
UIはユーザーインターフェース、アプリのウィンドウやボタンとかのコントロールのことらしい
UIスレッド(メインスレッド)と別スレッド(ワーカースレッド)
普通の処理は全部UIスレッド
時間のかかる処理をUIスレッドで行うと終了するまでUI(アプリ)が固まる
これを避けるためにUIスレッドとは別スレッドで時間のかかる処理を実行する
別スレッド
別スレッドを使うにはasyncとawait、System.ThreadingクラスのTask.Runを使う
これでUIスレッドは自由になるからUI(アプリ)は固まることがなくなるので
表示更新やボタンをクリックとかできるはずなんだけど
別スレッドからはUIにアクセスできない
UIスレッドからはボタンとかのコントロールにアクセスできるけど
別スレッドからはボタンとかのコントロールにアクセスできない
進捗率表示を更新したい
進捗率は別スレッドで行っている時間のかかる処理中に変化するから、別スレッドから表示更新することになる、けど別スレッドからUIにはアクセスできないっていう矛盾
これを解決するのに使うのがProgressクラス
ProgressBarのValueとTextBlockのTextを更新するメソッドを用意しておいて
これをProgressのインスタンス作成時に渡す(70行目)
受け取るときの引数の型はIProgressで受け取る(88行目)
(100行目)
キャンセルの方法もだいたい同じ感じで、使うのはCancellationTokenSourceクラス
CancellationTokenSourceのCancelメソッドでキャンセルを発行するようにしておいて
別スレッド実行前にCancellationTokenSourceのインスタンスを作成して
そのTokenプロパティを
別スレッドで実行する時間のかかる処理に渡す
95行目、キャンセルされていたら処理中止
表示更新はProgress
キャンセルはCancellationTokenSourceの違いくらいで同じ感じだねえ
これらを別スレッドとして実行するメソッドに渡して中でそれぞれの動作をさせる
参照したところ
WPF/Windowsフォーム:時間のかかる処理をバックグラウンドで実行するには?(async/await編)[C#/VB]:.NET TIPS - @IT
http://www.atmarkit.co.jp/ait/articles/1512/02/news019.html
【C# WPF】Task Runの引数にControl.Textを使うと別のスレッドに所有され... - Yahoo!知恵袋
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11182269146
非同期で複数処理を実行し、対話式で制御する - Qiita
https://qiita.com/haminiku/items/cc299c1ed94d7ba3f9ec
コード
アプリ
関連記事
2015/11/7、あれからもう3年…なの?
BackgroundWorkerの使い方メモ.NET VB、大量の画像ファイルを読み込んでListViewに縮小画像を表示 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/13625471.html
今ではBackgroundWorkerよりも今回の記事の方法のがいいみたいねえ