C# 带有回调的.NET异步webservice调用
我们有一个遗留的VB6应用程序,它使用一个用C#(.NET4.5)编写的ASMXWebService,它反过来使用一个库(C#/.NET4.5)来执行一些业务逻辑。其中一个库方法触发一个长时间运行的数据库存储过程,最后我们需要启动另一个进程,该进程使用存储过程生成的数据。因为其中一个要求是控件必须在调用Web服务后立即返回VB6客户端,所以库方法是C# 带有回调的.NET异步webservice调用,c#,asp.net,web-services,asynchronous,async-await,C#,Asp.net,Web Services,Asynchronous,Async Await,我们有一个遗留的VB6应用程序,它使用一个用C#(.NET4.5)编写的ASMXWebService,它反过来使用一个库(C#/.NET4.5)来执行一些业务逻辑。其中一个库方法触发一个长时间运行的数据库存储过程,最后我们需要启动另一个进程,该进程使用存储过程生成的数据。因为其中一个要求是控件必须在调用Web服务后立即返回VB6客户端,所以库方法是async,将操作回调作为参数,Web服务将回调定义为匿名方法,而不等待库方法调用的结果 从高层次来看,它是这样的: using System; us
async
,将操作
回调作为参数,Web服务将回调定义为匿名方法,而不等待
库方法调用的结果
从高层次来看,它是这样的:
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...
}
}
}
}
使用制度;
使用System.Data.SqlClient;
使用System.Threading.Tasks;
使用System.Web.Services;
名称空间示例
{
[WebService(Namespace=“urn:Services”)]
[WebServiceBinding(ConformsTo=WsiProfiles.BasicProfile1_1)]
公共类MyWebService
{
[网络方法]
公共字符串请求(字符串请求)
{
//步骤1:调用library方法生成数据
var lib=新的MyLibrary();
lib.GenerateDataAsync(()=>
{
//步骤2:启动使用步骤1中创建的数据的流程
});
返回“某种响应”;
}
}
公共类图书馆
{
公共异步任务GenerateDataAsync(操作onDoneCallback)
{
尝试
{
使用(var cmd=new-SqlCommand(“MyStoredProc”),new-SqlConnection(“mydb连接字符串”))
{
cmd.CommandType=System.Data.CommandType.StoredProcess;
cmd.CommandTimeout=0;
cmd.Connection.Open();
//异步调用存储过程。
wait cmd.ExecuteNonQueryAsync().ConfigureWait(false);
//如果提供了回调,则调用该回调。
if(onDoneCallback!=null)
onDoneCallback.Invoke();
}
}
捕获(例外情况除外)
{
//处理错误。。。
}
}
}
}
上述方法在本地测试中有效,但当代码部署为Web服务时,即使步骤1存储过程完成并生成数据,也不会执行步骤2
知道我们做错了什么吗?我找到了一个解决问题的方法,它涉及到异步执行代码的旧式(开始/结束)方法:
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
回调操作作为第二个参数传递给BeginExecutenQuery
方法,然后在AsyncCallback
中使用(第一个参数)。无论是在VS内部调试还是部署到IIS时,这都是一种魅力。让任务在IIS上运行是危险的,应用程序域可能在方法完成之前关闭,这很可能是您遇到的情况。如果您使用,您可以告诉IIS有正在发生的工作需要保持运行。这将使应用程序域多活90秒(默认情况下)
如果您想要比额外90秒更可靠的东西,请参阅Stephen Cleary的文章以了解其他一些选项。您的本地计算机的防火墙可能正在阻止传入连接…顺便说一下,我猜步骤1正在调用
lib.GenerateDataAsync
(未显示GenerateData
)。我认为问题在于asmx请求已经执行完毕,并且“已经出门了”,并且回调“无处执行”。您是否尝试过等待该呼叫?请检查您的请求执行情况。如果您使用的是async,您必须在整个管道中使用它,以获得最一致的解决方案。@MarkLarter,感谢您注意我的输入错误,我已经修复了它。在web服务中等待调用将破坏运行存储过程的整个“触发并忘记”方法。我同意,可能的原因是,在调用完成时,运行对GenerateDataAsync
的调用的线程被IIS回收,因此回调没有上下文可供执行。我只是希望“曾经在那里,做过那个”的人能提出一个解决办法。@Caspiananuck肯定是BTDT,但不是从.NET2.0(BeginAsync…)开始。等待仍然应该释放IIS工作线程来处理其他web请求,这样您就不会阻塞请求管道。我不确定你还需要通过“开火并忘记”来完成什么,所以不可否认,这可能不足以满足你的需要。
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...
}
}
}
}