我们有一个遗留的VB6应用程序,它使用用C#(.NET 4.5)编写的ASMX Web服务,后者又使用库(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服务第2步时,即使 Step 1 存储过程完成并生成数据,也永远不会执行
知道我们做错了吗?
答案 0 :(得分:2)
将任务保留在IIS上运行是危险的,应用程序域可能会在方法完成之前关闭,这可能是您发生的事情。如果您使用HostingEnvironment.QueueBackgroundWorkItem
,则可以告诉IIS有正在进行的工作需要继续运行。这将使app域保持活动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”以获得其他选择。
答案 1 :(得分:1)
我找到了一个解决我的问题的方法,该方法涉及异步执行代码的旧式(Begin / End)方法:
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时都像魅力一样。