いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

ADO.NET入門記-007 (コマンドの非同期実行(System.Data.SqlClient.SqlCommand,BeginExecuteReader,EndExecuteReader))


SQL Serverに関してのみのやり方ですが、SQL Serverの場合は、コマンドの非同期実行の仕組みがSqlCommandオブジェクトに
用意されています。System.Data.OracleClient等には、同じ機能は存在しませんのでご注意を。


非同期実行のやり方は、delegateの非同期実行のやり方と同じです。
BeginXXXXで非同期実行を開始し、EndXXXXで非同期処理を完了します。


非同期処理を行なう際の典型的なパターンは、以下のようになります。
これは、delegateの非同期実行する場合と同じです。

using(SqlCommand command = conn.CreateCommand()){

    command.CommandText = "xxxxx";

    //
    // 非同期処理を開始.
    // 第一引数は、非同期処理が終わった際にコールバックされるメソッド。第二引数はIAsyncResult.AsyncStateオブジェクトとして
    // 取得できるオブジェクト。
    //
    IAsyncResult asyncResult = command.BeginExecuteReader(new AsyncCallback(AsyncCallbackMethod), command);
}

private void AsyncCallbackMethod(IAsyncResult result){
    //
    // BeginExecuteReaderメソッドに渡したCommandオブジェクトを取得.
    // (EndExecuteReaderを呼ぶために必要)
    //
    SqlCommand command = null;
    try{

        command = (SqlCommand) result.AsyncState;

        //
        // 非同期処理を完了し、データを読み取る.
        //
        using(SqlDataReader reader = command.EndExecuteReader(result)){
            //
            // 読み取り処理・・・・
            //
        }

    }finally{
        if(command != null){
            command.Dispose();
        }
    }
}

また、C# 2.0からは匿名デリゲートが利用できるので上のものは以下のようにも
書けます。

using(SqlCommand command = conn.CreateCommand()){

    command.CommandText = "xxxxx";

    IAsyncResult asyncResult 
            = command.BeginExecuteReader(
                    delegate(IAsyncResult result){

                        SqlCommand command = null;
                        try{
                            command = (SqlCommand) result.AsyncState;

                            using(SqlDataReader reader = command.EndExecuteReader(result)){
                                //
                                // 読み取り処理・・・・
                                //
                            }
                        }finally{
                            if(command != null){
                                command.Dispose();
                            }
                        }
                    }
                    ,command
              );
}


以下サンプルです。
今回は、以下のテーブルが存在するとします。

create table AdoNetSample008Table(
     id int identity primary key
    ,name varchar(50) not null
    ,age int
    ,address varchar(200)
    ,inserted datetime
    ,updated datetime
);
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Transactions;
using System.Threading;

using NUnit.Framework;

using Gsf.Lib.UnitTests;

namespace Gsf.Samples.AdoNet {

    [TestFixture()]
    public class AdoNetSample008 : TestBase{

        [Test()]
        public void Commandの実行を非同期で行なってみる(){

            using(DbConnection conn = DbProviderFactories.GetFactory(_settings.ProviderName).CreateConnection()){

                conn.ConnectionString = _settings.ConnectionString;
                conn.Open();
                
                //
                // テスト用のデータをInsert.
                //
                Debug.WriteLine("準備用データの書き込み開始。");

                TruncateTable(conn, "AdoNetSample008Table");
                using(TransactionScope tx = new TransactionScope()){
                    PrepareDataInsert(conn);
                    tx.Complete();
                }

                Debug.WriteLine("準備用データの書き込み終了。");

                //
                // コマンド生成及び発行
                //
                using(DbCommand command = conn.CreateCommand()){

                    command.CommandText = "select id, name, age, address, inserted, updated from AdoNetSample008Table order by id";

                    IAsyncResult asyncResult = *1 {

                                    int count = 0;

                                    if(reader.HasRows){

                                        foreach(DbDataRecord record in reader){
                                            count++;
                                        }
                                    }

                                    Debug.WriteLine(string.Format("[非同期処理] 取得件数:{0}件", count));
                                }

                            }finally{

                                if(sqlCommand != null){
                                    sqlCommand.Dispose();
                                }
                            }

                            Debug.WriteLine("[非同期処理] データの読み取りが完了しました。");
                        } 
                        ,command
                    );

                    //
                    // 非同期で読み込み処理を実行し、その間待ち合わせする。
                    //
                    for(int i = 0; i < 5; i++){
                        Debug.WriteLine("待ち合わせ中・・・・・");
                        Thread.Sleep(50);
                    }

                    Debug.WriteLine(string.Format("IAsyncResult.IsCompletedの値={0}", asyncResult.IsCompleted));
                }
            }
        }

        private void PrepareDataInsert(DbConnection conn){

            using(DbCommand command = conn.CreateCommand()) {
                command.CommandText = "insert into AdoNetSample008Table (name, age, address, inserted, updated) values (@Name, @Age, @Address, @Inserted, @Updated)";

                DbParameter nameParam       = command.CreateParameter();
                nameParam.ParameterName     = "@Name";

                DbParameter ageParam        = command.CreateParameter();
                ageParam.DbType             = System.Data.DbType.Int32;
                ageParam.ParameterName      = "@Age";

                DbParameter addressParam    = command.CreateParameter();
                addressParam.ParameterName  = "@Address";

                DbParameter insertedParam   = command.CreateParameter();
                insertedParam.DbType        = DbType.DateTime;
                insertedParam.ParameterName = "@Inserted";

                DbParameter updatedParam    = command.CreateParameter();
                updatedParam.DbType         = DbType.DateTime;
                updatedParam.ParameterName  = "@Updated";

                command.Parameters.AddRange(new DbParameter[] { nameParam, ageParam, addressParam, insertedParam, updatedParam });

                for(int i = 0; i < 10000; i++) {
                    command.Parameters["@Name"].Value     = string.Format("gsf_zero{0}", i);
                    command.Parameters["@Age"].Value      = i;
                    command.Parameters["@Address"].Value  = string.Format("address_{0}", i);
                    command.Parameters["@Inserted"].Value = DateTime.Now;
                    command.Parameters["@Updated"].Value  = DateTime.Now;

                    command.ExecuteNonQuery();
                }
            }
        }
    }
}


実行してみると、非同期処理は完了しているはずなのに、最後に出力するIAsyncResult.IsCompletedの値が
Falseのままになってしまいます。何故だろ・・・。とりあえず、コールバックは正常に呼ばれているので
オッケイですが。


でも、実際非同期で処理する場合は、delegateを使うかBackgroundWorkerでやったり
する方が楽なのではないでしょうか。今回のものは知識として覚えておく程度でいいと思います。

*1:System.Data.SqlClient.SqlCommand)command).BeginExecuteReader( delegate(IAsyncResult result){ Debug.WriteLine("[非同期処理] 非同期処理が完了しました。データの読み取りを行ないます。"); System.Data.SqlClient.SqlCommand sqlCommand = null; try{ sqlCommand = (System.Data.SqlClient.SqlCommand)result.AsyncState; using(DbDataReader reader = sqlCommand.EndExecuteReader(result