for WPF developers
非同期処理をおこなうための方法はいくつかありますが、C# 5.0 以降では async/await 演算子を用いることで非同期処理に関する記述が簡潔になりました。 ここでは async/await 演算子の基本的な使い方について紹介します。
サンプルプロジェクトはここからダウンロードできます。
ページ内リンク
ここでは次のような UI のアプリケーションを作成します。
上記のサンプル UI の XAML は次のようになります。
<Window x:Class="AsyncSample.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainView" Height="300" Width="300">
<StackPanel>
<Button Content="Click me!" Command="{Binding ButtonCommand}" />
<TextBlock Text="{Binding Result}" />
<TextBox Text="テキスト入力" />
<CheckBox Content="Check me!" />
</StackPanel>
</Window>
上記のサンプル UI でデータバインド設定した ButtonCommand プロパティおよび
Result プロパティを実装した ViewModel を次のように定義します。
namespace AsyncSample.ViewModels
{
using System.Threading;
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("重たい処理をコールします。");
HeavyWork();
System.Console.WriteLine("重たい処理をコールしました。");
},
_ =>
{
return !IsBusy;
}));
}
}
#endregion 公開プロパティ
/// <summary>
/// 重たい処理を実行します。
/// </summary>
private void HeavyWork()
{
IsBusy = true;
Result = "只今実行中...";
System.Console.WriteLine("重たい処理を実行します。");
// 何か重たい処理
Thread.Sleep(3000);
System.Console.WriteLine("重たい処理を実行しました。");
Result = "終了しました。";
IsBusy = false;
}
}
}
ここでは重たい処理を HeavyWork() メソッドで表現しています。 このメソッドでは重たい処理を仮想的に表現するために Sleep() メソッドで 3[s] 間待機するようにしています。 また、その前後で Result プロパティおよび IsBusy プロパティを変更することで、 重たい処理を実行しているときとそうでないときを区別しようとしています。
しかし、このままでは重たい処理は同期的に処理されるため、ボタンが押されるとこの処理が終わるまで応答が返ってこなくなってしまいます。
重たい処理をコールします。
重たい処理を実行します。
重たい処理を実行しました。
重たい処理をコールしました。
また、重たい処理の前後で Result プロパティや IsBusy プロパティを変更していますが、 その変化は UI に反映されていません。 これもやはり重たい処理の部分でプログラムが返ってこないためです。 そして、プログラムが返ってきたときにはもう Result プロパティは "終了"、 IsBusy プロパティは false に戻ってしまうため、 UI の見た目には実行中の動作は現れてきません。
それでは上記のコードを非同期処理に書き直します。
先ほどの HeavyWork() メソッドを HeavyWorkAsync() メソッドからコールする形にします。
/// <summary>
/// 重たい処理を非同期で実行します。
/// </summary>
private async void HeavyWorkAsync()
{
IsBusy = true;
Result = "只今実行中...";
System.Console.WriteLine("Thread[{0}] 非同期処理を実行します。", Thread.CurrentThread.ManagedThreadId);
await HeavyWork();
// ここで一旦 return される
// 非同期処理が終了したらここから再開する
System.Console.WriteLine("Thread[{0}] 非同期処理を実行しました。", Thread.CurrentThread.ManagedThreadId);
Result = "終了しました。";
IsBusy = false;
}
/// <summary>
/// 重たい処理を実行するタスクを返します。
/// </summary>
/// <returns>重たい処理を実行するタスク</returns>
private Task HeavyWork()
{
return Task.Run(() =>
{
System.Console.WriteLine("Thread[{0}] 重たい処理を実行します。", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(3000);
System.Console.WriteLine("Thread[{0}] 重たい処理を終了します。", Thread.CurrentThread.ManagedThreadId);
});
}
await 演算子を使ってコールされるメソッドは必ず System.Threading.Tasks.Task クラスを返す必要があります。 これは、await 演算子によってメソッドを実行するのではなくタスクを実行するためです。 このため、HeavyWork() メソッドの戻り値を Task クラスとし、 その中身は Task.Run() メソッドを使って、 スレッドプール上に配置するタスクのタスクハンドルを返すようにします。 実際の処理は Run() メソッドの中にデリゲートの形で記述します。
最後に、HeavyWorkAsync() メソッドを呼ぶように ButtonCommand プロパティの中身を書き換えます。
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;
}));
}
}
以上の変更によって、Fig.2 に示したように重たい処理が非同期処理されるようになります。
また、出力ウィンドウへの出力は
Thread[10] 非同期処理をコールします。
Thread[10] 非同期処理を実行します。
Thread[12] 重たい処理を実行します。
Thread[10] 非同期処理をコールしました。
Thread[12] 重たい処理を終了します。
Thread[10] 非同期処理を実行しました。
上記で示しているコードの中のコメントにも書いてあるように、 async/await による非同期処理をおこなった場合、 それぞれのスレッドで実行されるコードの順番が少し複雑になります。 ここで紹介しているコードと実行結果のスレッド ID を照らし合わせながら 順に追って把握しておくことをお勧めします。
Designed by CSS.Design Sample