2

非同期 WCF メソッドで ADO.Net の BeginExecuteReader メソッドを使用しようとしていますが、取得できません。

次の契約とサービス コードがあります。サービスのbeginメソッドにコールバックメソッドの詳細を入力する方法がわかりません。これに関する Web 上の例や MSDN のドキュメントが見つからないため、どんな助けも大歓迎です。これを行う方法に完全に混乱しているため、サンプルコードへのリンクでも役立ちます。

契約コード:

    [ServiceContract(Namespace = ServiceConstants.ServiceContractNamespace,
    Name = ServiceConstants.ServiceName)]
    public interface IAsyncOrderService
    {
       [OperationContract(AsyncPattern=true)]
       IAsyncResult BeginGetProducts(string vendorId, AsyncCallback callback,
                                                object state);

       List<Product> EndGetProducts(IAsyncResult result);
    }

サービスコードは次のとおりです。

    public IAsyncResult BeginGetProducts(string vendorId, AsyncCallback cb, object s)
    {
        DocumentsSummaryByProgram summary = null;

        SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Conn1"].ConnectionString);
        SqlCommand sqlCmd = null;


        sqlCmd = new SqlCommand("dbo.GetProducts", conn);
        sqlCmd.CommandType = CommandType.StoredProcedure;

        sqlCmd.Parameters.AddWithValue("@vendorId", sqlCmd);

        conn.Open();

        return sqlCmd.BeginExecuteReader(cb, vendorId);
    }

    public List<Product> EndGetProducts(IAsyncResult r)
    {
        List<Product> products = new List<Product>();
        SqlCommand cmd = r.AsyncState as SqlCommand;
        if (cmd != null)
        {
            SqlDataReader dr = cmd.EndExecuteReader(r);
            while (dr.Read())
            {
                //do your processing here and populate products collection object
            }
        }

        return products;
    }

更新 1: これは不可能な作業のようです。Microsoft は、ADO.Net 非同期メソッドが WCF から非同期で呼び出される方法を示す例を提供する必要がありました。これは、スケーラブルになりたい多くのアプリに役立つためです。

更新 2: WCF で非同期パターンを正常に実装できた後、質問に対する詳細な回答を提供しました。答えは下の別記事をご覧ください。

4

2 に答える 2

3

あなたは開いたSqlConnection

conn.Open();

また、次の 2 つのSqlConnectionオブジェクトを作成しました。

SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Conn1"].ConnectionString);

と:

sqlCmd = new SqlCommand("dbo.GetProducts", new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["VHA_EDM"].ConnectionString));

編集

非同期コールバックを追加するには、次のようにします。

var callback = new AsyncCallback(HandleCallback);
sqlCmd.BeginExecuteReader(callback, command);

BeginExecuteReader間に実行する予定の非同期コードがなくEndExecuteReaderExecuteReader.

編集 2

AsyncCallbackデリゲートには次の署名があります。

public delegate void AsyncCallback(IAsyncResult ar);

そのデリゲート メソッド内から、メソッドを作成できInvokeますEndGetProducts

編集 3

を使用してデータを取得する例を次に示しますBeginExecuteReader

public SqlCommand Command { get; set; }

public IAsyncResult BeginGetStuff()
{
    var connect = "[enter your connection string here]";
    // Note: Your connection string will need to contain:
    // Asynchronous Processing=true;

    var cn = new SqlConnection(connect);
    cn.Open();

    var cmd = new SqlCommand("[enter your stored proc name here]", cn);
    cmd.CommandType = CommandType.StoredProcedure;
    this.Command = cmd;

    return cmd.BeginExecuteReader();
}

public List<string> EndGetStuff(IAsyncResult r)
{
    var dr = this.Command.EndExecuteReader(r);
    var list = new List<string>();
    while (dr.Read())
        list.Add(dr[0].ToString());
    return list;
}
于 2012-12-24T01:05:08.893 に答える
1

それはかなり長い答えなので、私は私の質問に答えるために別の投稿を提供しています。他の人がWCFに非同期パターンをすばやく実装するのに役立つことを願っています。WCFで非同期パターンを実装するときに見逃していた点は次のとおりです。これらがないと、「接続中...」というWCFのハングの問題が発生するか、WCFレベルで操作が中止/キャンセルされたエラーメッセージが表示されていました。以下の私のソリューションでは、単純にするために、WCF側の非同期パターンでの例外処理については説明していません。

  • delagateInstance.Invokeまたはその他の方法を使用して呼び出すように、コードでWCFのEndGetProductsメソッドを呼び出さないでください。非同期パターンでは、長い非同期操作が完了したときにクライアント側のコールバックを呼び出すだけです。これにより、クライアント側のコールバックが呼び出され、WCF EndGetProductメソッドが呼び出されます(例:cb( asyncResult1)ここで、cbは、このWCFを呼び出すクライアント側のコードによって渡されるコールバックデリゲートインスタンスです。Invokeを使用してEndGetProductsWCFメソッドを呼び出そうとしましたが、これは間違っています。クライアント側がクライアントコールバックに何も渡さない場合でも、これはWCFでEndメソッドを呼び出すために実行する必要があります。
  • ADO.Net非同期begindatareaderメソッドから取得したasyncresultをBeginGetProductsメソッドから返さないでください。これは、クライアントのWCF呼び出しのコンテキストにあるAsyncResultと同じである必要があるためです。つまり、クライアント側がこれらに何も渡していない場合でも、BeginGetProductsが返すAsyncResultにクライアント側のコールバックとクライアント側の状態オブジェクトを含める必要があります。BeginGetProductsからADO.Net非同期メソッドbegindatareaderのAsyncResultを返していましたが、これは間違っています。
  • WCFからクライアント側のコールバックデリゲートインスタンスを呼び出すときは、前の箇条書きで説明したクライアント側のコンテキストを含むAsyncResultを渡すようにしてください。また、非同期操作が完了したときにこれを実行します。これは、Listオブジェクトを作成した後のbeginexecutereaderのコールバックで実行します。
  • 最後に覚えておくべき点は、非同期操作にはかなり長い時間がかかる可能性があるため、WCFおよびADO.Netレベルで十分に大きなタイムアウトを設定する必要があることです。そうしないと、WCFでタイムアウトが発生します。このためには、ADO.Netコマンドのタイムアウトを0(無限のタイムアウト)または適切な値に設定します。WCFの場合は、次のような構成を含めることができます。

    <binding name="legacyBinding" openTimeout="00:10:00" sendTimeout="00:10:00"    
    receiveTimeout="00:10:00" closeTimeout="00:10:00"  maxBufferPoolSize="2147483647" 
    maxReceivedMessageSize="2147483647" >
    

コードは長く見えるかもしれませんが、私の意図は、他の人がWCFに非同期パターンを簡単に実装できるようにすることです。とても大変でした。

WCF契約

    [OperationContract(AsyncPattern = true)]
    [FaultContract(typeof(string))]
    IAsyncResult BeginGetProducts(string vendorId, AsyncCallback cb, object s);
    //The End method must return the actual datatype you intend to return from 
    //your async WCF operation. Also, do not decorate the End method with 
    //OperationContract or any other attribute
    List<Product> EndGetProducts(IAsyncResult r);

WCFの実装

       public IAsyncResult BeginGetProducts( string vendorId, AsyncCallback cb, object s)
    {

        SqlCommand sqlCmd = null;
        sqlCmd = new SqlCommand("dbo.ABC_sp_GetProducts", "Data Source=xyz;Initial Catalog=NorthwindNew;Integrated Security:true;asynchronous processing=true;"));
        sqlCmd.CommandType = CommandType.StoredProcedure;
        sqlCmd.Parameters.AddWithValue("@vendorId", vendorId);
        sqlCmd.CommandTimeout = 0;//async operations can be long operations so set a long timeout

        //THIS ASYNRESULT MUST REFLECT THE CLIENT-SIDE STATE OBJECT, AND IT IS WHAT SHOULD FLOW THROUGH TO END METHOD of WCF.
        //THE CLIENT CALLBACK (PARAMETER 'cb') SHOULD BE INVOKED USING THIS ASYNCRESULT, ELSE YOUR WCH WILL HANG OR YOUR WCF WILL GET ABORTED AUTOMATICALLY.
        AsyncResult<FinalDataForDocumentsSummary> asyncResult1 = new AsyncResult<FinalDataForDocumentsSummary>(false, s);//this is the AsyncResult that should be used for any WCF-related method (not ADO.Net related)

        AsyncCallback callback = new AsyncCallback(HandleCallback);//this is callback for ADO.Net async begindatareader  method


        sqlCmd.Connection.Open();

        //AsynResult below is for passing information to ADO.Net asyn callback
        AsyncResult<Product> cmdResult = new AsyncResult<Product>(false, new object[] {sqlCmd, cb,s});

        sqlCmd.BeginExecuteReader(HandleCallback, cmdResult);


         return asyncResult1;//ALWAYS RETURN THE ASYNCRESULT INSTANTIATED FROM CLIENT PARAMETER OF STATE OBJECT. FOR DATAREADER CREATE ANOTHER ASYNCRESULT THAT HAS COMMAND OBJECT INSIDE IT.
    }


     /// <summary>
     /// This is the callback on WCF side for begin data reader method.
     /// This is where you retrieve data, and put it into appropriate data objects to be returned to client.
     /// Once data has been put into these objects, mark this ASYNC operation as complete and invoke the
    ///  client callback by using 'cb(asyncResult1)'. Use the same asyncresult that contains the client passed state object.
     /// </summary>
     /// <param name="result"></param>
    public void HandleCallback(IAsyncResult result)
    {
        List<Product> summaries = new List<Product>();
        Product product = null;

        //THIS ASYNCRESULT IS ONLY FOR DATAREADER ASYNC METHOD AND NOT TO BE USED WITH WCF, ELSE BE READY FOR WCF FAILING
        AsyncResult<Product> asyncResult = result.AsyncState as AsyncResult<Product>;

        object[] objects = asyncResult.AsyncState as object[];
        SqlCommand cmd = objects[0] as SqlCommand;
        AsyncCallback cb = objects[1] as AsyncCallback;
        object s = objects[2];
       //CREATE THE SAME ASYNCRESULT THAT WE HAD IN BEGIN METHOD THAT USES THE CLIENT PASSED STATE OBJECT
        AsyncResult<Product> asyncResult1 = new AsyncResult<Product>(false, s);


        SqlDataReader dr = null;
        if (cmd != null)
        {
            try
            {
                  dr = cmd.EndExecuteReader(result);
                while (dr.Read())
                {
                    product = new Product(dr.GetInt32(0), dr.GetString(1));
                    summaries.Add(summary);
                }


                dr.Close();
                cmd.Connection.Close();

                //USE THE CORRECT ASYNCRESULT. WE NEED THE ASYNCRESULT THAT WE CREATED IN BEGIN METHOD OF WCF.
                asyncResult1.Data = new FinalDataForDocumentsSummary(count, summaries.OrderByDescending(x => x.CountOfOverDue).ToList());

            }
            finally
            {
                if (dr != null)
                {
                    dr.Close();
                }
                if (cmd.Connection != null)
                {
                    cmd.Connection.Close();
                    cmd.Connection.Dispose();
                }

                //USE THE CORRECT ASYNCRESULT. WE NEED THE ASYNCRESULT THAT WE CREATED IN BEGIN METHOD OF WCF
                asyncResult1.Complete();

                //THIS IS REQUIRED ELSE WCF WILL HANG. EVEN WHEN NO CALLBACK IS PASSED BY CLIENT,
                //YOU MUST EXECUTE THIS CODE. EXECUTE IT AFTER YOUR OPERATION HAS COMPLETED, 
                //SINCE THIS IS WHAT CAUSES THE END METHOD IN WCF TO EXECUTE. 
                //DON'T TRY TO CALL THE WCF END METHOD BY YOUR CODE (like using delegateInstance.Invoke) SINCE THIS WILL HANDLE IT.
                cb(asyncResult1);

            }
        }


    }
    /// <summary>
    /// This method gets automatically called by WCF if you include 'cb(asyncResult1)' in the reader's callback meethod, so don't try to call it by your code. 
    /// But always use 'cb(asyncResult1)' just after data has been successfully retrieved from database and operation is marked as complete.
    /// </summary>
    /// <param name="r"></param>
    /// <returns></returns>
    public List<Product> EndGetProducts(IAsyncResult r)
    {

       AsyncResult<Product> result = r as AsyncResult<Product>;


       // Wait until the AsyncResult object indicates the 
       // operation is complete, in case the client called the End method just after the Begin method.
        if (!result.CompletedSynchronously)
        {
            System.Threading.WaitHandle waitHandle = result.AsyncWaitHandle;
            waitHandle.WaitOne();
        }

        // Return the database query results in the Data field
        return result.Data;

    }

非同期パターンで必要なAsyncResultのジェネリッククラス

using System;
using System.Threading;


class AsyncResult<T> : IAsyncResult
{
    private T data;
    private object state;
    private bool isCompleted = false;
    private AutoResetEvent waitHandle;
    private bool isSynchronous = false;

    public T Data
    {
        set { data = value; }
        get { return data; }
    }

    public AsyncResult(bool synchronous, object stateData)
    {
        isSynchronous = synchronous;
        state = stateData;
    }

    public void Complete()
    {
        isCompleted = true;
        ((AutoResetEvent)AsyncWaitHandle).Set();
    }

    public object AsyncState
    {
        get { return state; }
    }

    public WaitHandle AsyncWaitHandle
    {
        get
        {
            if (waitHandle == null)
                waitHandle = new AutoResetEvent(false);

            return waitHandle;
        }
    }

    public bool CompletedSynchronously
    {
        get
        {
            if (!isCompleted)
                return false;
            else
                return isSynchronous;
        }
    }

    public bool IsCompleted
    {
        get { return isCompleted; }
    }
}

クライアント側からこれを呼び出す方法:

    protected void Page_Load(object sender, EventArgs e)
    {
        using (ABCService.ServiceClient sc = new ABCService.ServiceClient())
        {
           // List<ABCService.Product> products = sc.GetDocSummary("Vend1", null, false);//this is synchronous call from client
          sc.BeginGetProducts("Vend1",GetProductsCallback, sc);//this is asynchronous call from WCF

        }

    }

    protected void GetProductsCallback(IAsyncResult asyncResult)
    {
        List<ABCService.Product> products = ((ABCService.ServiceClient)asyncResult.AsyncState).EndGetProducts(asyncResult);//this will call the WCF EndGetProducts method

    }
于 2012-12-25T19:08:56.210 に答える