C# .NET Web服务异步实现

4

我正在尝试实现一个使用Web服务执行一些非常耗时任务的API。我希望Web服务启动一个线程来执行这个长时间的任务,并让它一直运行直到完成。问题在于,由于这是一个API,我希望它跨平台。因此,我的问题如下:

  • 是否可能进行异步调用而不需要客户端在.NET Framework上?(似乎已经有了Begin/End框架,需要返回.NET IASyncResult对象)如果可以,应该如何做?提供明确无误的示例代码将非常有帮助。
  • 由于Web服务中没有状态保留,这个线程可以稍后恢复吗?如果客户端想要取消进程,这将非常重要。
4个回答

4
好的,请稍等一下。
如果您希望用户可以进行异步调用,则他们具有控制权,您只需响应即可。如果您希望在您的一侧完成异步工作,则需要采用不同的模式。其中一种模式是设置一个调用工作的方法,然后设置另一个方法来查看工作的状态。第一个方法返回一些类型的令牌/标识符,可以用于在另一个调用中检查状态。然后您需要第三个方法来获取结果。这都是客户端驱动的。
理论上,您可以设置回调机制,但是客户端需要有一种获取答案的方式,这是其端口的服务。这更加复杂,而且不太适合“公共API”的性质,但它可以与客户端配合使用。
系统的异构性不应成为因素。并且,您可以同时应用两种模式,使得可以进行Web服务的客户端进行fire and forget,而其他客户端则进行轮询。
轮询的唯一缺点是增加了服务器的负载,因此请设置适当的期望值,并准备好对执行此操作的人进行限流。
while(thread.NoComplete())
{
   pollTheCrapOutofService();
}

如果你可以应对异构环境(开放标准等),并能强制使用.NET,你有其他选择,正如Craig所提到的那样。


你描述的模式正是我想实现的。那么它是否需要Begin/End模式,或者我是否可以在Web方法中启动一个线程?我预见到的问题是如何找到该线程以便在客户端调用这样的方法时挂起/恢复/中止它。 - jrbalsano
假设每个用户只有一个调用,您可以将线程ID存储在数据库中。然后当用户调用停止时,您可以使用该ID发送kill命令。 - Chad
如果您只是想执行某些工作,可以启动线程并等待返回。至少在大多数情况下,上下文应该包含在请求和响应中,这样您就可以将响应与请求匹配。如果无法轻松将其与键值匹配,则必须通过将请求存储在数据库中创建键值。然后,具有响应的请求将等于已完成的任务(可供轮询)。希望这可以帮助到您。 - Gregory A Beamer

2
这是一个不错的起点: http://msdn.microsoft.com/zh-cn/library/aa480516.aspx 如果您实现了这种模式,在客户端方面,没有任何变化。您可以实现第二个 Web 方法来检查您在异步方法中排队的任何正在运行的作业的状态。
从示例代码中,稍作修改以让您了解需要做什么(我不希望这将编译):
[WebService]
public class AsyncWebService : System.Web.Services.WebService
{
public delegate string LengthyProcedureAsyncStub(
    int milliseconds, MyState state);

public string LengthyProcedure(int milliseconds, MyState state) 
{ 
    while(state.Abort == false)
    {
          //Do your work.  Check periodically for an abort
    }
    return state.Abort ? "Aborted" : "Success"; 
}

//This state object is what you can use to track invocations of your method
//You'll need to store it in a thread safe container.  Add it to the container in the Begin method and remove it in the end method.  While it's in the container other web methods can find it and use it to monitor or stop the executing job.
public class MyState 
{ 
    public Guid JobID = Guid.NewGuid();
    public object previousState; 
    public LengthyProcedureAsyncStub asyncStub; 
    public bool Abort = false;
}

[ System.Web.Services.WebMethod ]
public IAsyncResult BeginLengthyProcedure(int milliseconds, 
    AsyncCallback cb, object s)
{
    LengthyProcedureAsyncStub stub 
        = new LengthyProcedureAsyncStub(LengthyProcedure);
    MyState ms = new MyState();
    ms.previousState = s; 
    ms.asyncStub = stub;
    //Add to service wide container
    return stub.BeginInvoke(milliseconds, cb, ms);
}

[ System.Web.Services.WebMethod ]
public string EndLengthyProcedure(IAsyncResult call)
{
    //Remove from service wide container
    MyState ms = (MyState)call.AsyncState;
    return ms.asyncStub.EndInvoke(call);
}

[WebMethod]
public void StopJob(Guid jobID)
{
     //Look for the job in the service wide container
     MyState state = GetStateFromServiceWideContainer(jobID);
     state.Abort = true;
}
}

对于客户端来说,他们正在调用一个名为LenghtyProcedure的Web方法,该方法将不会返回直到作业完成。


这是很好的信息,但有点过时了。我认为现在人们更喜欢他们的网络服务更加RESTful而不是SOAPy。 - Robert Harvey
实际上,我要做的任务是创建一个SOAP API...所以这可能是我想要走的方向。我正在快速阅读这篇文章,但它似乎给了我需要返回.NET对象的Begin/End模式。不过我感觉自己可能忽略了什么。 - jrbalsano
@Redian - 如果你认为在这个例子中 IAsyncResult 被传回给了客户端,那么你是错的。框架将 BeginXXX/EndXXX 对暴露给客户端时简单地表示为 XXX。在你的 BeginXXX 实现中返回的 IAsyncResult 是框架内部使用的。 - Joe Enzminger
那么它只是返回结果,而不等待异步方法调用完成吗?(我的大脑现在几乎要爆炸了——就像盯着一个视觉错觉看了5个小时)。但是如果我需要取消异步调用,那么如何追踪它呢? - jrbalsano
有很多方法可以做到这一点,这只是其中之一。祝你完成作业好运 - 这是一个具有挑战性的任务! - Joe Enzminger
显示剩余2条评论

1

我们使用AppFabric Workflow服务来实现此功能。

它们作为WCF服务公开,因此任何可以执行SOAP调用的东西或任何其他支持的WCF传输都可以调用它们。

免费包括持久性(如果您设置了SQL Server)、监控和管理,并且.NET客户端会自动生成。

一个潜在的缺点是:您需要IIS 7+和.NET 4。


我对AppFabric不熟悉。理想情况下,客户会在本地托管API,而IIS会随着他们使用的Windows版本一起提供,那么要求他们托管另一个服务是否有点过分了? - jrbalsano
如果您想托管Web服务,那么您需要一个Web服务器。AppFabric在IIS之上运行。 - Craig Stuntz
IIS随Windows商业版本作为可添加/可移除的Windows功能一同提供。那么AppFabric也需要与Web服务一同打包安装程序,是吗? - jrbalsano
除了服务本身,是的。它只是一个MSI安装程序。或者您可以从Web平台安装程序获取它。就像我说的,更有意义的限制是IIS 7。这意味着它无法在Server 2003上运行。您需要Vista+或Server 2008+。 - Craig Stuntz

1
是否可以进行异步调用而不需要客户端使用.NET Framework?(似乎现有的Begin/End框架需要我返回.NET IASyncResult对象)如果可以,如何实现?
向客户端公开REST API。然后,您只需要一层代码来在.NET异步对象和REST服务之间进行转换。
由于Web服务中没有状态保留,因此可以稍后恢复该线程吗?
您可以公开一个REST方法来返回操作的状态,以及另一个REST方法来取消异步操作。
对于长时间运行的操作,我通常会设置一个带有ThreadPool的Windows服务。您可以在那里公开您的IAsync API。
在IIS服务器上公开REST API的最简单方法是使用简单的ASP.NET MVC应用程序。您可以直接从控制器方法执行长时间运行的异步进程。

可以麻烦您提供一个可能带我朝这个方向的链接吗?如果我选择它,我将需要通过我的主管运行课程更改(从SOAP到REST)。 - jrbalsano
@Redian:啊,我明白了;你已经在使用SOAP了。嗯,关于使用ASP.NET MVC创建REST API的文档有点不完整,所以既然你不熟悉ASP.NET MVC,我会推荐你去http://www.codeproject.com/KB/WCF/RestServiceAPI.aspx,这里描述了如何使用WCF创建REST API。但是无论你使用SOAP还是REST,原理都是一样的:创建一个代码层,用于在你的Web服务API和你的.NET长时间运行的异步API之间进行转换。 - Robert Harvey
那就不应该有问题了。我正在为一个已经编写好的基于线程的解决方案实现一个API。基本上,那应该作为接口层。这正是我在寻找的。如果我使用ThreadPool设置一个服务,我应该能够追踪线程吗? - jrbalsano
是的,如果基于线程的解决方案在其API中有用于完成此操作的内容。有很多方法可以做到这一点。其中一种方法是将某种唯一的任务令牌交回给启动长时间运行服务的原始SOAP调用者(它可以简单地作为“任务编号”);然后他们可以在另一个SOAP调用中使用该令牌来查询进程状态或取消进程。 - Robert Harvey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接