for WPF developers
重たい処理をおこなう場合に非同期処理をよく用いますが、 その処理の進捗状況がわからないと、 本当に処理が実行されているのかどうかがわかりません。 ここでは async/await 演算子による非同期処理で進捗状況を取得する例を紹介します。
サンプルプロジェクトはここからダウンロードできます。
ページ内リンク
ここでは「async/await による非同期処理 その 1」で示したサンプルプログラム の UI 部分をそのまま流用します。
非同期処理の記述は「async/await による非同期処理 その 1」で示したサンプルプログラムとほぼ同じですが、 一部だけ変更したいと思います。
まず、ViewModel が公開するプロパティは以下のようにそのまま流用します。
namespace AsyncSample3.ViewModels
{
using System;
using System.Threading;
using System.Threading.Tasks;
public class MainViewModel : NotificationObject
{
#region 公開プロパティ
private string result = "まだ実行してないよ。";
/// <summary>
/// 実行結果を取得または設定します。
/// </summary>
public string Result
{
get { return result; }
set { SetProperty(ref result, value); }
}
private bool isBusy;
/// <summary>
/// ビジー状態かどうかを取得または設定します。
/// </summary>
public bool IsBusy
{
get { return isBusy; }
set
{
if (SetProperty(ref isBusy, value))
ButtonCommand.RaiseCanExecuteChanged();
}
}
private DelegateCommand buttonCommand;
/// <summary>
/// ボタンコマンドを取得します。
/// </summary>
public DelegateCommand ButtonCommand
{
get
{
return buttonCommand ?? (buttonCommand = new DelegateCommand(
_ =>
{
System.Console.WriteLine("Thread[{0}] 非同期処理をコールします。", Thread.CurrentThread.ManagedThreadId);
HeavyWorkAsync();
System.Console.WriteLine("Thread[{0}] 非同期処理をコールしました。", Thread.CurrentThread.ManagedThreadId);
},
_ =>
{
return !IsBusy;
}));
}
}
#endregion 公開プロパティ
}
}
そして、ButtonCommand プロパティから呼ばれる HeavyWorkAsync() メソッドもそのまま流用します。
/// <summary>
/// 重たい処理を非同期で実行します。
/// </summary>
private async void HeavyWorkAsync()
{
IsBusy = true;
Result = "只今実行中...";
System.Console.WriteLine("Thread[{0}] 非同期処理を実行します。", Thread.CurrentThread.ManagedThreadId);
await HeavyWorkTask();
// ここで一旦 return される
// 非同期処理が終了したらここから再開する
System.Console.WriteLine("Thread[{0}] 非同期処理を実行しました。", Thread.CurrentThread.ManagedThreadId);
Result = "終了しました。";
IsBusy = false;
}
ここからが少し違います。上記コードで HeavyWork() メソッドをコールしていた部分が HeavyWorkTask() メソッドをコールしていますね。
HeavyWorkTask() メソッドは次のようなコードになります。
/// <summary>
/// 重たい処理を実行するタスクを返します。
/// </summary>
/// <returns>重たい処理を実行するタスク</returns>
private Task HeavyWorkTask()
{
return Task.Run(() =>
{
// 重たい処理を実行
System.Console.WriteLine("Thread[{0}] 重たい処理を実行します。", Thread.CurrentThread.ManagedThreadId);
HeavyWork();
System.Console.WriteLine("Thread[{0}] 重たい処理を終了します。", Thread.CurrentThread.ManagedThreadId);
});
}
/// <summary>
/// 重たい処理を実行します。
/// </summary>
private void HeavyWork()
{
int count = 0;
while (count++ < 100)
{
// 重たい処理
Thread.Sleep(30);
}
}
このままでは単に HeavyWork() メソッドを非同期処理するだけで、 その進捗状況を知ることができません。次は進捗状況を取得できるように Code 3 のメソッドを変更していきます。
進捗状況を報告するには IProgress<T> インターフェースを使用します。
HeavyWork() メソッドを次のように変更します。
/// <summary>
/// 重たい処理を実行します。
/// </summary>
/// <param name="progress">進捗を示すための <code>System.IProgress<int></code> を実装したクラスを指定します。</param>
private void HeavyWork(IProgress<int> progress)
{
int count = 0;
while (count++ < 100)
{
// 重たい処理
Thread.Sleep(30);
// 進捗率の更新
progress.Report(count);
}
}
続いて、HeavyWork() メソッドを呼び出す HeavyWorkTask() メソッドを次のように変更します。
/// <summary>
/// 重たい処理を実行するタスクを返します。
/// </summary>
/// <returns>重たい処理を実行するタスク</returns>
private Task HeavyWorkTask()
{
return Task.Run(() =>
{
// 進捗報告用クラスのインスタンス生成
var p = new Progress<int>(i =>
{
// 進捗報告は UI スレッドで実行されるため、
// データバインドされたプロパティも操作できる
Result = "只今実行中..." + " [" + i.ToString() + "%]";
});
// 重たい処理を実行
System.Console.WriteLine("Thread[{0}] 重たい処理を実行します。", Thread.CurrentThread.ManagedThreadId);
HeavyWork(p);
System.Console.WriteLine("Thread[{0}] 重たい処理を終了します。", Thread.CurrentThread.ManagedThreadId);
});
}
Progress<T> クラスのコンストラクタでは、 Report() メソッドがコールされたときに実行される処理を指定します。 このとき、その入力引数は Report() メソッドをコールしたときに渡した T 型のパラメータとなります。 ここでは Result プロパティを、この入力引数を含めた形で変更しています。
通常、UI 更新に関連するプロパティ等は UI スレッド上からのみアクセスが許可されているため、 例えばデータバインド機能によって UI と連携している Result プロパティは、 UI スレッド以外で実行されている HeavyWork() メソッド内からは操作できず、 仮に操作しようとすると例外が発生してしまいます。 しかし、IProgress<T> の Report() メソッドは、 その指定された処理の実体は UI スレッド上で処理されるため、 上記のように Result プロパティにアクセスすることができます。
進捗率の計算に関してはそれぞれのアプリケーションによって異なります。
今回のサンプルでは、1 〜 100 までの数値を Report() メソッドに渡し、
これを進捗率として [%] で表記しましたが、
Report() メソッドで渡す数値が [%] そのものではないかもしれないし、
そもそも処理の全体を知っているのは HeavyWork() メソッドではなく Progress クラスのインスタンスを持つクラスのほうかもしれません。
この部分はそれぞれのアプリケーションに応じて変更することになります。
Designed by CSS.Design Sample