我正在尝试在asyn WCF方法中使用ADO.Net的BeginExecuteReader方法,但无法获取它。
我有以下合同和服务代码。我无法理解如何在begin方法服务中填写回调方法的详细信息。 非常感谢任何帮助,因为我无法在网上找到任何示例或MSDN上的任何文档。甚至一些示例代码的链接也会有所帮助,因为我完全不知道如何做到这一点。 / p>
合同代码:
[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应该提供示例来说明如何以异步方式从WCF调用ADO.Net异步方法,因为这对于那些希望可扩展的应用程序非常有用。
更新2 :在我能够在WCF中成功实现异步模式之后,我已经提供了对我的问题的详细解答。请在下面的单独帖子中查看答案。
答案 0 :(得分:3)
你从未打过电话打开SqlConnection
conn.Open();
您还创建了两个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
和EndExecuteReader
之间运行的任何异步代码,那么最好只使用ExecuteReader
。
修改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;
}
答案 1 :(得分:1)
我提供了一个单独的帖子来回答我的问题,因为这是一个很长的答案。我希望它可以帮助其他人在他们的WCF中快速实现异步模式。 在WCF中实现异步模式时我遗漏的要点如下。没有这些,我要么得到一个挂起的WCF问题,说“正在连接......”或者操作在WCF级别被中止/取消错误消息。在下面的解决方案中,我没有讨论WCF方面的异步模式中的异常处理,以保持简单。
要记住的最后一点是您必须在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
}