本記事では非同期処理の簡単な使い方をサンプルプログラムとともに紹介します。
時間のかかる処理などは別のスレッドで実行させることで、ソフトが操作不能になることを回避したり、並列して処理を行うことができるようになります。今回は、非同期処理で終了を「待つ/待たない」両方のサンプルを紹介します。
非同期処理・別スレッドとは
ソフトウェアにおける、処理の流れをスレッドと呼びます。メインのスレッドで時間のかかる処理を行ってしまうと、他の処理全てがその処理が終わるのを待たなければなりません。
このような状況を回避するために、メインのスレッドとは別のスレッド(別スレッド)に処理を実行させることが可能です。このように並列して処理を行うことを「非同期処理」といいます。
非同期処理で使用するTask.Runとasync/await
Taskは、スレッドとスレッドで行う処理の結果をひとまとめにして管理します。
Task.Run()として記載することで、処理をまとめて別スレッドで実行することができます。
awaitをつけたTask.Run()はその処理(別スレッド)が終了するまで呼び出しスレッドを止めます。
awaitを使用するメソッドにはasyncを付けて宣言する必要があります。
サンプルプログラム(待つ/待たない処理)
ボタンを押すと非同期処理を実行できるシンプルなサンプルプログラムを示します。MVVM形式で書いていますが、ViewModelを見れば別形式の方でも参考になると思います。
1つ目のボタンは、非同期処理の終了を待ち、続きの処理が実行されます(awaitあり)。
2つ目のボタンは、非同期処理の終了を待たず、続きの処理が実行されます。
非同期処理では「時間のかかる処理」を模したカウント処理(0-5)を行います。カウントは画面右のListViewに追加され表示されます。
「END」のテキストが挿入されるタイミングが違うところに注目です。
View
XAMLは以下の通り。
<Window x:Class="AsyncSample00.Views.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainView" Height="250" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="20" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="20" /> <ColumnDefinition Width="2*" /> </Grid.ColumnDefinitions> <Button Grid.Row="1" Grid.Column="0" Margin="10" Content="START(1)" Command="{Binding StartBtn1_Pushed}" /> <Button Grid.Row="2" Grid.Column="0" Margin="10" Content="START(2)" Command="{Binding StartBtn2_Pushed}" /> <TextBlock Grid.Row="0" Grid.Column="2" Text="◇Output"/> <ListView Grid.Row="1" Grid.Column="2" Grid.RowSpan="3" Margin="5" ItemsSource="{Binding rsltColl}"/> </Grid> </Window>
コードビハインドは以下の通り。
namespace AsyncSample00.Views { public partial class MainView : Window { public MainView() { InitializeComponent(); MainViewModel vm = new MainViewModel(); this.DataContext = vm; } } }
ViewModel
2つのボタンに紐づけられたコマンドがあります。
別スレッドからUIを更新するためにApp.Current.Dispatcher.Invokeを使用しています。ないとエラーになります。
namespace AsyncSample00.ViewModels { public class MainViewModel:INotifyPropertyChanged { //コマンド public ICommand StartBtn1_Pushed { get; set; } public ICommand StartBtn2_Pushed { get; set; } public MainViewModel() { //出力表示用のコレクション rsltColl = new ObservableCollection<string>(); //コマンド StartBtn1_Pushed = new RelayCommand(Start1_cmd); StartBtn2_Pushed = new RelayCommand(Start2_cmd); } //重い処理を含むメソッド(0-5をゆっくりカウント) private void HeavyMethod() { for (int i = 0; i <=5 ; i++) { App.Current.Dispatcher.Invoke((Action)(() => { rsltColl.Add(i.ToString()); })); //重い処理想定で停止させる Thread.Sleep(1000); } } //非同期処理の終了を待つ場合 private async void Start1_cmd() { //出力要素のクリア rsltColl.Clear(); rsltColl.Add(" - START1 - "); await Task.Run(() => HeavyMethod()); rsltColl.Add(" - END1 - "); } //非同期処理の終了を待たない場合 private void Start2_cmd() { //出力要素のクリア rsltColl.Clear(); rsltColl.Add(" - START2 - "); Task.Run(() => HeavyMethod()); rsltColl.Add(" - END2 - "); } //出力要素表示用のコレクション private ObservableCollection<string> _rsltColl; public ObservableCollection<string> rsltColl { get { return _rsltColl; } set { _rsltColl = value; NotifyPropertyChanged("rsltColl"); } } private string _txt1; public string txt1 { get { return _txt1; } set { _txt1 = value; NotifyPropertyChanged("txt1"); } } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } } }
本記事内容と直接関係ない部分は省略しています。引数あり、別スレッドの停止なども後に記事作成しようと思います(多分)。
さいごに
Taskは便利です。awaitの有無で処理の順番が大きく変わるので順序を意識してコーディングしたいですね。
MVVM形式の作り方は以下記事を参考にどうぞ。
引数、戻り値のある非同期処理は以下記事で扱っています。
コメント