【问题标题】:WCF Windows Service - Long operations/Callback to calling moduleWCF Windows 服务 - 长操作/回调到调用模块
【发布时间】:2011-01-24 21:49:09
【问题描述】:

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

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

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

【问题讨论】:

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


    【解决方案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. 使用方法定义Service Interface,传递长操作所需的参数(参考之前CallBackContractAttribute中的ILongOperationCallBack接口)

      [ServiceContract(CallbackContract=typeof(ILongOperationCallBack))]
      public interface ILongOperationService
      {
          [OperationContract]
          bool StartLongOperation(...);
      }
      
    3. 在实现Service接口的Service类中,先获取客户端回调的代理,保存在类字段中,然后异步启动long操作工作,立即返回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的类来接收结果,并在服务端添加一个启动长操作的方法(启动方法和事件管理器不需要在同一个类中)

      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 return 来指示服务器是 Busy 而不是 down,但只有在我的实际应用中长操作不能并发时才需要,并且可能存在是 WCF 中实现非并发的最佳方法(要发现服务器是否已关闭,请照常添加 Try/Catch 块)。

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

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

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

    【讨论】:

      【解决方案2】:

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

      (2) 对该服务的额外调用将​​启动新线程。

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

      【讨论】:

      • 你确定(2)?是否有文章解释了这种行为或关闭这些服务线程的方法?
      【解决方案3】:

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

      但是,在我看来,这种机制相当不稳定,不值得推荐。

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

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

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

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

      这比每台 Windows 服务器机器中都存在的消息队列 (MSMQ) 更有效(但似乎没有很多人知道或使用它):

      • 您的客户端在请求队列中丢弃请求
      • 该服务侦听该请求队列并在请求后获取请求并且它是否有效
      • 然后该服务可以将结果发布到结果队列中,而您的调用者又会在该队列上监听

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

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

      【讨论】:

      • 我无法使用 MSMQ,因为此 Windows 服务在用户系统(XP、Vista、7)上运行,并且是用户下载的产品的一部分。所以我猜 GetStatus() 是要走的路。想知道同时执行多个命令会有多复杂。
      • @A9S6:WCF 为每个调用创建一个全新的服务实例——因此同时多个请求确实没有问题——每个请求都有自己的、小的、独立的服务类实例。跨度>
      • 你的意思是每次调用都会创建一个服务类的新实例(继承的ServiceBase)?我认为这仅发生在 Web 服务而不是 Windows 服务中。那么,除非每个方法调用都需要,否则不应将初始化放在此类中?
      • @A9S6:是的 - 每次调用 WCF 服务(默认情况下)都会创建一个新的服务类实例来处理请求。但这是一件好事 !!
      猜你喜欢
      • 1970-01-01
      • 2012-06-27
      • 1970-01-01
      • 1970-01-01
      • 2013-07-28
      • 1970-01-01
      • 1970-01-01
      • 2016-09-30
      • 2014-04-13
      相关资源
      最近更新 更多