带回调的.NET异步web服务调用
我们有一个传统的VB6应用程序,它使用用C#(.NET 4.5)编写的ASMX webservice,后者又使用库(C#/ .NET 4.5)来执行一些业务逻辑。 其中一个库方法会触发长时间运行的数据库存储过程,最后我们需要启动另一个使用存储过程生成的数据的进程。 因为其中一个要求是控件必须在调用webservice后立即返回到VB6客户端,库方法是async
,需要将Action
回调作为参数,webservice将回调定义为匿名方法并且不会await
结果库方法调用。
在高层次看起来像这样:
using System; using System.Data.SqlClient; using System.Threading.Tasks; using System.Web.Services; namespace Sample { [WebService(Namespace = "urn:Services")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class MyWebService { [WebMethod] public string Request(string request) { // Step 1: Call the library method to generate data var lib = new MyLibrary(); lib.GenerateDataAsync(() => { // Step 2: Kick off a process that consumes the data created in Step 1 }); return "some kind of response"; } } public class MyLibrary { public async Task GenerateDataAsync(Action onDoneCallback) { try { using (var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string"))) { cmd.CommandType = System.Data.CommandType.StoredProcedure; cmd.CommandTimeout = 0; cmd.Connection.Open(); // Asynchronously call the stored procedure. await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); // Invoke the callback if it's provided. if (onDoneCallback != null) onDoneCallback.Invoke(); } } catch (Exception ex) { // Handle errors... } } } }
上述工作在本地测试中,但是当代码部署为web服务时,即使第1步存储过程完成并生成数据, 第2步也不会执行。
任何想法我们做错了什么?
将任务保留在IIS上运行是非常危险的,应用程序域可能会在方法完成之前关闭,这可能是您正在发生的事情。 如果您使用HostingEnvironment.QueueBackgroundWorkItem
,则可以告诉IIS有需要继续运行的工作。 这将使应用程序域保持额外的90秒(默认情况下)
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Web.Services;
namespace Sample
{
[WebService(Namespace = "urn:Services")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class MyWebService
{
[WebMethod]
public string Request(string request)
{
// Step 1: Call the library method to generate data
var lib = new MyLibrary();
HostingEnvironment.QueueBackgroundWorkItem((token) =>
lib.GenerateDataAsync(() =>
{
// Step 2: Kick off a process that consumes the data created in Step 1
}));
return "some kind of response";
}
}
public class MyLibrary
{
public async Task GenerateDataAsync(Action onDoneCallback)
{
try
{
using (var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string")))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandTimeout = 0;
cmd.Connection.Open();
// Asynchronously call the stored procedure.
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
// Invoke the callback if it's provided.
if (onDoneCallback != null)
onDoneCallback();
}
}
catch (Exception ex)
{
// Handle errors...
}
}
}
}
如果你想要超过90秒的可靠性,请参阅Stephen Cleary的文章“Fire and Forget on ASP.NET”以获取其他一些选择。
我已经找到了解决我的问题的方法,它涉及到异步执行代码的旧式(开始/结束)方法:
public void GenerateData(Action onDoneCallback)
{
try
{
var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string"));
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandTimeout = 0;
cmd.Connection.Open();
cmd.BeginExecuteNonQuery(
(IAsyncResult result) =>
{
cmd.EndExecuteNonQuery(result);
cmd.Dispose();
// Invoke the callback if it's provided, ignoring any errors it may throw.
var callback = result.AsyncState as Action;
if (callback != null)
callback.Invoke();
},
onUpdateCompleted);
}
catch (Exception ex)
{
// Handle errors...
}
}
onUpdateCompleted
回调操作作为第二个参数传递给BeginExecuteNonQuery
方法,然后在AsyncCallback
(第一个参数)中使用。 这在VS内部调试和部署到IIS时都很有用。
上一篇: .NET async webservice call with a callback
下一篇: Portname and Servicename are not getting changed in Java WebService