async/await 非同期メソッド

.NET Framework 4.5 に対応した C# 5.0 や VB 11.0 で搭載された「非同期メソッド」(=async修飾子とawait演算子を利用したメソッド)による非同期処理実装技術を紹介します。
async/await を使うことでこれまで苦労してきたUIタスクの制御がとても楽になるので、ぜひ覚えてほしいテクニックです。

 

 

 

1. 単純なサンプル - ボタンクリックからラベル変更

 

[概要]
画面上のボタンをクリックすると、画面上のラベルの文字が1秒周期で10秒間書き換わる、というサンプルを例に記載する。

 

[環境]

 

1-1. 非同期を使わない例(うまく動作できない例)

同期処理による実装サンプルコードを以下に記載します。
プログラムコードはとてもシンプルになりますが、処理中はウィンドウの移動やリサイズが動作しませんし、ラベルの表示をせっかく行っているのに最後を除いてすべて無視されます。実際には使用できない実装例です。

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;
        }
    }
}

 

1-2. Thread を使った例 (.NET1.1以降)

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();
        }
    }
}

 

1-3. TaskFactory を使った例 (.NET4.0以降)

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)
                {
                    // スレッド動作中にアプリを終了した場合の異常処理
                    // ここでは例外を無視
                }
            });
        }
    }
}

 

1-4. async/await を使った例(その1)(.NET4.5以降)

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;
        }
    }
}

 

1-5. async/await を使った例(その2)(.NET4.5以降)

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();
        }
    }
}

 

サンプルプログラム

ダウンロード

 

 

2. async/await で例外を処理するには

[概要]

async/await で例外を処理するには、いくつか押さえておくべき点があります。

  1. await はそのままの例外を、Wait は AggregateException 例外にラップされて、送出する。
  2. 返り値を持たない非同期メソッドは await できない。そのメソッドから例外を送出されると、例外をキャッチできずにプログラムは異常終了してしまう。
    → 返り値を持たない非同期メソッドを書くときは、全ての例外をメソッド内で処理するようにする必要がある。
  3. 複数のタスクを並列実行した場合に発生したすべての例外を知るには工夫が必要。

これらを確認していきましょう。

 

[参考]

[環境]

 

記載途中

 

 

ライセンス

本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。

The MIT License (MIT)

  Copyright 2017 Kinoshita Hidetoshi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 

 

参考

 


 

記載

2017-10-08 - 新規作成

 

Programming Items トップページ

プライバシーポリシー