.NET Framework 4.5 に対応した C# 5.0 や VB 11.0
で搭載された「非同期メソッド」(=async修飾子とawait演算子を利用したメソッド)による非同期処理実装技術を紹介します。
async/await
を使うことでこれまで苦労してきたUIタスクの制御がとても楽になるので、ぜひ覚えてほしいテクニックです。
[概要]
画面上のボタンをクリックすると、画面上のラベルの文字が1秒周期で10秒間書き換わる、というサンプルを例に記載する。
[環境]
同期処理による実装サンプルコードを以下に記載します。
プログラムコードはとてもシンプルになりますが、処理中はウィンドウの移動やリサイズが動作しませんし、ラベルの表示をせっかく行っているのに最後を除いてすべて無視されます。実際には使用できない実装例です。
using System.Windows;
namespace Sample01
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
click_button.IsEnabled = false;
for ( int i=0; i<=10; ++i)
{
System.Threading.Thread.Sleep(1000);
counter_label.Content = i.ToString();
}
click_button.IsEnabled = true;
}
}
}
class Thread による実装例です。
期待する動作を実現することができますが、ラベルやボタンを制御するために
Dispatcher.Invoke による命令を必要とします。プログラムコードは複雑になります。
using System.Threading; // Thread
using System.Threading.Tasks; // TaskCanceledException
using System.Windows;
namespace Sample02
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// ボタンを無効化
click_button.IsEnabled = false;
// ワーカースレッドを作成
var th = new Thread(() =>
{
for ( int i=0; i<=10; ++i)
{
try
{
this.Dispatcher.Invoke(() =>
{
// UIタスクへ、ラベルの文字変更を指示
counter_label.Content = i.ToString();
});
Thread.Sleep(1000);
}
catch(TaskCanceledException)
{
// スレッド動作中にアプリを終了した場合の異常処理
// ここでは例外を無視
break;
}
}
try
{
this.Dispatcher.Invoke(() =>
{
// UIタスクへ、ボタン有効化を指示
click_button.IsEnabled = true;
});
}
catch(TaskCanceledException)
{
// スレッド動作中にアプリを終了した場合の異常処理
// ここでは例外を無視
}
});
// ワーカースレッドを実行
th.Start();
}
}
}
Task および TaskFactory による実装例です。
期待する動作を実現することができますが、ラベルの表示更新のために
Dispatcher.Invoke による命令を必要とします。プログラムコードは複雑になります。
Thread
を使った例とほぼ同じ内容になります。Task を終えた後の処理を簡単に実現できることはメリットかと思われます。
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Sample03
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// ボタンを無効化
click_button.IsEnabled = false;
// ワーカースレッドを作成
var task = new TaskFactory().StartNew(() => {
int i = 0;
for (i = 0; i < 10; ++i)
{
try
{
this.Dispatcher.Invoke(() =>
{
// UIタスクへ、ラベルの文字変更を指示
counter_label.Content = i.ToString();
});
Thread.Sleep(1000);
}
catch (TaskCanceledException)
{
// スレッド動作中にアプリを終了した場合の異常処理
// ここでは例外を無視
break;
}
}
return i;
});
// 上の処理が終わったら続けて以下の処理を行います。
task.ContinueWith((x) =>
{
try
{
this.Dispatcher.Invoke(() =>
{
// Task.Result (intです) を使ってラベルも変えてみます。
counter_label.Content = x.Result;
// UIタスクへ、ボタン有効化を指示
click_button.IsEnabled = true;
});
}
catch (TaskCanceledException)
{
// スレッド動作中にアプリを終了した場合の異常処理
// ここでは例外を無視
}
});
}
}
}
async/await を使うと、以上で説明した処理をとても簡単に記載できるようになります。
await
内の処理は実際には別タスクとして処理されるので、動作としては以上で説明した Thread や Task による処理と同等です。
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Sample04
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
// ボタンを無効化
click_button.IsEnabled = false;
int i = 0;
for ( i=0; i<10; ++i)
{
// label 更新
counter_label.Content = i.ToString();
// 1秒待機
await Task.Run(() => {
Thread.Sleep(1000);
});
}
// label 更新
counter_label.Content = i.ToString();
// ボタンを有効化
click_button.IsEnabled = true;
}
}
}
async/await を使う例としてもう一例を記載します。
(その1)とほぼ同じですが、Lambda式でデリゲートを一旦登録後に実行しているということが違いです。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Sample05
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Action func = async () =>
{
// ボタンを無効化
click_button.IsEnabled = false;
int i = 0;
for (i = 0; i < 10; i++)
{
// label 更新
counter_label.Content = i.ToString();
// 1秒待機
await Task.Run(() =>
{
Thread.Sleep(1000);
});
}
// label 更新
counter_label.Content = i.ToString();
// ボタンを有効化
click_button.IsEnabled = true;
};
func();
}
}
}
[概要]
async/await で例外を処理するには、いくつか押さえておくべき点があります。
これらを確認していきましょう。
[参考]
[環境]
記載途中
本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。
2017-10-08 | - | 新規作成 |