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

时间:2010-03-08 09:16:42

标签: c# .net wcf windows-services callback

我有一个Windows服务,它取一堆文件的名称并对它们进行操作(zip / unzip,更新db等)。这些操作可能需要一些时间,具体取决于发送到服务的文件的大小和数量。

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

(2)如果调用了一个服务方法并且正在运行另一个对同一服务的调用,那么该调用将如何处理(我认为只有一个与该服务相关联的线程)。我已经看到,当服务在处理方法时花费时间时,与服务相关联的线程开始增加。

3 个答案:

答案 0 :(得分:14)

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

但是,在我看来,这种机制相当薄弱,并不值得推荐。

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

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

  • 使用该服务删除请求,然后“放手” - 这将是一个单向呼叫,您只需放弃您想要完成的任务,然后您的客户端完成
  • 有一个状态调用,客户端可以在给定时间后调用,以查明请求的结果是否已准备就绪
  • 如果是,则应该进行第三次服务调用以检索结果

因此,在您的情况下,您可以删除压缩某些文件的请求。该服务将启动并完成其工作并将生成的ZIP存储在临时位置。然后,客户端可以检查ZIP是否准备就绪,如果是,则检索它。

这比每个Windows服务器计算机中存在的消息队列(MSMQ)更好用(但很多人似乎都不知道或使用它):

  • 您的客户端在请求队列中删除请求
  • 服务侦听该请求队列并在请求后提取请求并运行
  • 然后,服务可以将结果发布到结果队列,您的呼叫者将依次监听

通过阅读优秀的MSDN文章Foudnations: Build a queue WCF Response Service,了解如何有效地完成所有这些工作 - 强烈推荐!

在我看来,基于消息队列的系统往往比基于双工/回拨合约的系统更稳定,更不容易出错。

答案 1 :(得分:1)

(1)实现这一点的最简单方法是使用taskId,然后使用另一个名为IsTaskComplete的方法,客户端可以使用该方法检查任务是否已完成。

(2)对服务的其他调用将启动新线程。

编辑:默认服务行为是每次调用启动新线程。可配置属性为Instance Context Mode,可以设置为PerCall,PerSession或Shareable。

答案 2 :(得分:1)

这个问题有一个解决方案,但是我使用WCF双工服务来获得长时间操作的结果,即使我发现一个问题花费了我几个小时来解决(而且'为什么我之前搜索过这个问题),现在它完美无缺,我相信它是WCF双工服务框架内的一个简单解决方案。

长时间操作有什么问题?主要问题是在服务器执行操作时阻塞客户端接口,并且通过WCF双工服务,我们可以使用回调客户端来避免阻塞(这是一种避免阻塞的旧方法,但它可以很容易地转换为使用TaskCompletionSource的async / await框架。

简而言之,该解决方案使用一种方法在服务器上异步启动操作并立即返回。结果准备就绪后,服务器将通过客户端回调来返回它们。

首先,您必须遵循任何标准指南来创建WCF双工服务和客户端,我发现这两个有用:

msdn duplex service

Codeproject Article WCF Duplex Service

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

  1. 使用事件管理器方法定义回调接口,以便从服务器发送结果并在客户端接收结果。

    public interface ILongOperationCallBack
    { 
        [OperationContract(IsOneWay = true)]
        void OnResultsSend(....);        
    }
    
  2. 使用方法定义服务接口以传递long操作所需的参数(请参阅CallBackContractAttribute中的前一个ILongOperationCallBack接口)

    [ServiceContract(CallbackContract=typeof(ILongOperationCallBack))]
    public interface ILongOperationService
    {
        [OperationContract]
        bool StartLongOperation(...);
    }
    
  3. 在实现服务接口的Service类中,首先获取客户端回调的代理并将其保存在类字段中,然后异步启动长操作并返回bool价值立即。当长操作工作完成后,使用客户端回调代理字段将结果发送到客户端。

    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. 在客户端创建一个实现ILongOperationCallBack的类来接收结果并添加一个方法来启动服务器中的长操作(start方法和事件管理器不需要在同一个类中)

    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
        }
    }
    
  5. 注意:

    1. 我在StartLongOperation方法中使用bool返回来指示服务器是忙而不是关闭,但只有当长操作不能像我的实际应用程序那样并发时才需要它,并且也许在WCF中有最好的方法来实现非并发(发现服务器是否已关闭,像往常一样添加Try / Catch块)。

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

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

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