WCF Windows服务-长时间操作/回调到调用模块

11

我有一个Windows服务,它接收一堆文件的名称并对这些文件执行操作(压缩/解压缩、更新数据库等)。根据发送到服务的文件的大小和数量,操作可能需要花费一定的时间。

(1) 发送请求给该服务的模块将等待文件被处理。我想知道是否有一种方式在服务中提供回调,以便在完成文件处理时通知调用模块。请注意,多个模块可以同时调用服务来处理文件,因此服务需要提供某种类型的TaskId。

(2) 如果调用了一个正在运行的服务方法,并且再次调用了同一个服务,那么该调用将如何被处理(我认为只有一个线程与该服务相关)。我发现当服务处理方法需要花费时间时,与服务关联的线程开始增加。

3个回答

14

WCF确实提供了双工绑定,允许您指定回调协定,以便服务可以回调到调用客户端进行通知。

然而,在我看来,这种机制相当不稳定,不建议使用。

在这种情况下,当调用导致一个相当长时间的操作发生时,我会这样做:

如果您想坚持使用HTTP/NetTcp绑定,我会:

  • 将请求发送给服务,然后“放手”——这将是一个单向调用,您只需放下要完成的内容,然后您的客户端就完成了
  • 有一个状态调用,客户端可以在一定时间后调用该调用,以查找请求的结果是否已经准备好
  • 如果是,应该有第三个服务调用来检索结果

因此,在您的情况下,您可以将压缩一些文件的请求放下。服务将离开并执行其工作,并将生成的ZIP存储在临时位置。然后稍后客户端可以检查ZIP是否准备好,如果准备好,就可以检索它。

这个方法甚至可以通过消息队列(MSMQ)来更好地实现,因为每台 Windows 服务器上都有它(但似乎很少有人知道它或使用它):

  • 您的客户端将请求放置在请求队列中
  • 服务监听该请求队列,并获取一个接一个的请求并处理它们
  • 然后,服务可以将结果发布到结果队列中,您的调用者依次监听该队列

通过阅读优秀的 MSDN 文章 Foudnations: Build a queue WCF Response Service,您可以有效地了解如何实现这一切 - 强烈推荐!

在我看来,基于消息队列的系统比基于双工/回调合同的系统更加稳定且出错率更低。


我无法使用MSMQ,因为此Windows服务在用户的系统(XP、Vista、7)上运行,并且是用户下载的产品的一部分。因此,我猜GetStatus()是前进的方式。不知道在多个命令同时执行时会变得多么复杂。 - A9S6
@A9S6:WCF为每个调用创建全新的服务实例 - 因此在同时进行多个请求时没有任何问题 - 每个请求都有自己独立的小型服务类实例。 - marc_s
您的意思是每次调用都会创建一个Service类(ServiceBase继承)的新实例吗?我以为这只会发生在Web服务而不是Windows服务中。因此,除非在每个方法调用时需要,否则不应将初始化放在此类中。 - A9S6
@A9S6:是的 - 每次调用 WCF 服务(默认情况下)都会创建一个新的服务类实例来处理请求。但这是一件好事!! - marc_s

1
(1)最简单的方法是使用taskId,然后再有另一个名为IsTaskComplete的方法,客户端可以通过该方法检查任务是否已完成。
(2)对服务进行的其他调用将启动新线程。
编辑:默认服务行为是每次调用都启动新线程。可配置属性为Instance Context Mode,可设置为PerCall、PerSession或Shareable。

你确定 (2) 吗?有没有一篇文章可以解释这种行为或者关闭这些服务线程的方法? - A9S6

1
这个问题有解决方案,但我正在使用WCF双工服务来获取长时间操作的结果。虽然我找到了一个问题,花费了我几个小时来解决(这就是为什么我之前搜索这个问题的原因),现在它完美地工作了,我相信这是WCF双工服务框架内的一个简单解决方案。
长时间操作的问题是什么?主要问题是在服务器执行操作时阻塞客户端界面,而使用WCF双工服务,我们可以使用回调来避免阻塞(这是一种旧的避免阻塞的方法,但可以很容易地转换成使用TaskCompletionSource的异步/等待框架)。
简而言之,解决方案使用一种方法在服务器上异步启动操作并立即返回。当结果准备好时,服务器通过客户端回调将它们返回。
首先,您必须遵循任何标准指南来创建WCF双工服务和客户端,我发现这两个有用:msdn duplex service

Codeproject Article WCF Duplex Service(WCF双工服务的Codeproject文章)

然后按照以下步骤添加自己的代码:

  1. Define the call back interface with an event manager method to send results from the server and receive them in the client.

    public interface ILongOperationCallBack
    { 
        [OperationContract(IsOneWay = true)]
        void OnResultsSend(....);        
    }
    
  2. Define the Service Interface with a method to pass the parameters needed by the long operation (refer the previous ILongOperationCallBack interface in the CallBackContractAttribute)

    [ServiceContract(CallbackContract=typeof(ILongOperationCallBack))]
    public interface ILongOperationService
    {
        [OperationContract]
        bool StartLongOperation(...);
    }
    
  3. In the Service class that implements the Service Interface, first get the proxy of the client call back and save it in a class field, then start the long operation work asynchronously and return the bool value immediately. When the long operation work is finished send the results to the client using the client call back proxy field.

    public class LongOperationService:ILongOperationService
    {
        ILongOperationCallBack clientCallBackProxy;
        public ILongOperationCallBack ClientCallBackProxy
        {
            get
            {
                return OperationContext.Current.GetCallbackChannel<ITrialServiceCallBack>());
            }
        }
    
        public bool StartLongOperation(....)
        {
            if(!server.IsBusy)
            {
                 //set server busy state
                //**Important get the client call back proxy here and save it in a class field.**
                this.clientCallBackProxy=ClientCallBackProxy;
                //start long operation in any asynchronous way
                ......LongOperationWorkAsync(....)
                return true; //return inmediately
            }
            else return false;
        }
    
        private void LongOperationWorkAsync(.....)
        {
            .... do work...
            //send results when finished using the cached client call back proxy
            this.clientCallBackProxy.SendResults(....);
            //clear server busy state
        }
        ....
    }
    
  4. In the client create a class that implements ILongOperationCallBack to receive results and add a method to start the long operation in the server (the start method and the event manager don't need to be in the same class)

    public class LongOperationManager: ILongOperationCallBack
    {
    
        public busy StartLongOperation(ILongOperationService server, ....)
        {
            //here you can make the method async using a TaskCompletionSource
            if(server.StartLongOperation(...)) Console.WriteLine("long oper started");
            else Console.Writeline("Long Operation Server is busy")
        }
    
        public void OnResultsSend(.....)
        {
            ... use long operation results..
            //Complete the TaskCompletionSource if you used one
        }
    }
    

注意事项:

  1. 在StartLongOperation方法中,我使用bool返回值来表示服务器忙碌而不是宕机,但仅当长操作不能并发时才需要,例如在我的实际应用程序中,可能有更好的方法在WCF中实现非并发(要发现服务器是否宕机,请像通常一样添加Try/Catch块)。

  2. 我没有看到的重要引用是需要在StartLongOperation方法中缓存回调客户端代理。我的问题是我试图在工作方法中获取代理(是的,所有示例都在服务方法中使用回调客户端代理,但文档中没有明确说明,在长操作的情况下,我们必须延迟回调直到操作结束)。

  3. 在服务方法返回后,在下一个服务方法之前不要获取和缓存两次回调代理。

免责声明:我没有添加控制错误等代码。


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