ステップ 7 ハンズオン : 非同期処理を使ったパフォーマンスの向上

Windows フォームにおけるパフォーマンスの向上 (非同期処理編)


マイクロソフト株式会社 デベロッパーマーケティング本部
デベロッパーエバンジェリスト 高橋 忍

最終更新日 2005 年 3 月 16 日

目標 重たい処理を実行する Windows フォームアプリケーションの作成
使用技術
  • Visual Basic .NET
取り上げるトピックス
  • Delegate を使った非同期処理による Windows アプリケーションの動作の違いを確認する
前提知識
目次
備考 DoEvents メソッドを使ったパフォーマンスの向上』で作成したプロジェクトを使用します。
サンプル アプリケーションの
完成品ダウンロード
Improve_performance_Winform2.exe (exe 形式 / 42.8 KB)

非同期処理を使ったパフォーマンス テスト アプリケーションの作成

では、delegate を使った非同期処理によるパフォーマンスの違いを確認するためのテスト アプリケーションを作成してみます。

Visual Studio .NET 2003 を起動して、作成したプロジェクトを開きます。このとき、myProc の sleep 時間は 500(ms)に戻しておきましょう。そして、ここに RadioButton コントロールを 1 つ追加します。

コントロール ID

プロパティ名

設定値

RadioButton3

(Name)

optDelegate

  

Text

非同期処理

図 1. アプリケーション フォーム

では非同期処理のための修正について検討します。実装しなければならない事は次の 4 つの処理になります。

  1. デリゲートを定義する
  2. デリゲートのインスタンスを作成し、実行するメソッドと紐付ける
  3. 非同期処理が終了した時に戻ってくるメソッドを準備する
  4. メソッド呼び出しの変わりにデリゲートを使った非同期処理を開始する

定義するデリゲートの名称を mydelegate とし、インスタンス名を dlgMyProc、非同期処理終了時に戻ってくるメソッドを comp メソッドとします。

まずは、デリゲートを定義します。btnStart_Click メソッドの前に以下のコードを追加します。

Private Delegate Sub mydelegate()

    Private Sub btnStart_Click( …

デリゲートは、非同期で実行する処理 (ここでは、業務処理) に渡すためのパイプに当たるものです。ですから実行する処理に引数を渡す場合には通常のメソッド定義と同様に引数を定義します。

続けてデリゲートのインスタンスを作成し、実行するメソッドと紐付けます。このデリゲート インスタンスは、メソッドを呼ぶ際に使用しますので、メソッドを実行する btnStart_Click に以下のように定義します。引数には実行するメソッドのプロシージャ デリゲート インスタンスを AddressOf 演算子を使って渡します。

Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As … 
        Dim dlgMyProc As New mydelegate(AddressOf myProc)

        Me.ListBox1.Items.Clear()
        Me.additem("start")
        Me.myProc()
        Me.additem("end")
    End Sub

このように定義するものだと覚えてしまいましょう。このように定義することで、myProc メソッドがデリゲート インスタンスと紐付けられます。

次に、非同期処理が終了した時に戻ってくるメソッドを準備します。なぜこのようなメソッドが必要なのでしょう?

非同期処理ではメソッドを呼び出すと、すぐに呼び出し元に処理が返ってきてしまいます。では非同期処理が終了したらどのように戻り値などを受け取る ことができるのでしょうか? 実はこのために用意されているのが、非同期処理が終了したときに戻ってくるメソッドなのです。ここで、非同期処理の完了通知を受け取り、必要に応じて戻り 値などを取得することができるのです。

では、実際に完了通知を受け取るためのメソッドを定義します。以下のメソッドをコードに追加します。

 

Private Sub comp(ByVal a As IAsyncResult)
        Additem("end")
    End Sub

完了通知を受け取る際には、IAsyncResult インターフェースを引数に定義します。このインターフェースを通して非同期処理の戻り値等を取得することができます。ここでは非同期処理終了時にリストボックスに書き込みをするように実装しておきます。

最後に、メソッド呼び出しの変わりにデリゲートを使った非同期処理を開始するように実装します。デリゲートを使った非同期処理の実行には BeginInvoke メソッドを使用しましょう。

Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As … 
        Dim dlgMyProc As New mydelegate(AddressOf myProc)

        Me.ListBox1.Items.Clear()
        Me.additem("start")

        If Me.optDelegate.Checked Then
            dlgMyProc.BeginInvoke(AddressOf comp, dlgMyProc)
        Else
            Me.myProc()
            Me.additem("end")
        End If
    End Sub

第 1 引数には完了通知を受け取るメソッドのプロシージャデリゲートインスタンスを AddressOf 演算子を使って渡します。第 2 引数に状態オブジェクトとして自身のオブジェクトを渡します。ここはこのように使うと覚えてしまいましょう。

さて、ここで大事な点があります。BeginInvoke で非同期処理を実行して処理が完了したら必ず、EndInvoke を実行しなければならないという事です。EndInvoke を実行しないとメモリーリークが発生する可能性がありますので、忘れずに実装しましょう。EndInvoke を実行するためのオブジェクトは完了通知のためのメソッドに渡されるオブジェクトから取り出すことができます。引数で渡されたオブジェクトをキャストし て、EndInvoke を実行しましょう。

以上で、アプリケーションの実装は完了です。では実際にビルトしてパフォーマンスのテストをしてみましょう。「ビルト」メニューから「ソリューショ ンのビルト」を実行してアプリケーションをビルトします。問題がなければ、「デバッグ」メニューから「デバッグ無しで開始」を実行します。

アプリケーションが起動したら、こんどは非同期処理のラジオボタンを選択してから「開始」ボタンを押してみましょう。開始メッセージが表示され 次々と処理完了のメッセージがリストボックスに追加されます。このときにウィンドウの移動をしてみると先ほどとは違って、スムースに処理が行われることがわかります。

図 2. 非同期処理によってスムースにアプリケーションを実行できる

デリゲートを使った非同期処理を実行したため、業務処理とその他のウィンドウ処理は並列に実行されるため (厳密には並列に実行されているわけでありませんが) それぞれが影響されずに処理を実行することができます。また、ウィンドウ操作をしても業務処理がとまることもありません。これならば使用者も安心して使う ことができるアプリケーションといえるでしょう。

しかし、このアプリケーションには問題が内在されています。Windows アプリケーションには 「window に対する操作はウィンドウを生成したスレッドから行わなければならない」という原則があります。それに対して、現在はウィンドウとは別のスレッドからリス トボックスに書き込む処理を行っています。一見正しく動いているように見えますが、その動作は保障されたものではありません。そのためこの部分を修正して やる必要があります。

 

次は 『非同期処理の注意点』について説明します。

 

ページのトップへ