0

背景:

我有一个在带有 NetTCP 绑定的 Windows 服务中托管 WCF 服务的系统。要将新服务添加到集合中,您只需在 <system.serviceModel -> services /> 中添加标准 WCF 配置条目,然后在自定义配置部分中添加一行,告诉宿主框架它需要初始化服务。每个服务都使用自己的后台线程和 AppDomain 实例进行初始化,以保持一切隔离。

以下是如何初始化服务的示例:

Host
  - ServerManager
    - ServiceManager 
      - BaseServerHost

ServerManager 实例有一个 ServiceManager 的集合,每个 ServiceManager 与一个服务实例相关联,这是标准 WCF 实现所在的位置(ServiceHost.Open/Close 等)。ServiceManager 实例通过使用 BaseServerHost 基类(抽象)实例化(基于配置 - 它具有标准程序集/类型定义)服务的实例。每个服务都必须从这里继承,以便框架能够使用它。作为初始化过程的一部分,BaseServerHost 公开了几个事件,特别是拥有的 ServiceManager 附加到的 UnhandledException 事件。(这部分对于下面的问题至关重要。)

整个过程对我们来说非常有效(一个实例运行 63 个服务),因为我可以带一个对 WCF 一无所知的人,他们可以非常快速地创建服务。

问题:

我遇到的问题是后台线程。我们端点上的大多数公开方法在标准插入/更新/删除方法调用(例如向其他系统发送消息)之后执行大量活动。为了保持性能(前端是基于网络的),我们让初始的插入/更新/删除方法完成它的工作,然后启动一个后台线程来处理最终用户不需要等待的所有东西去完成。此选项非常有效,直到该后台线程中的某些内容未处理并导致整个 Windows 服务关闭,我理解这是设计使然(我可以接受)。

根据我的所有研究,我发现没有办法实现全局 try/catch(减去使用启用 1.1 处理后台崩溃的黑客配置),因此我的团队将不得不返回并在适当的地方获取这些. 除此之外,我发现 WCF 托管的端点端似乎在每次调用时都在其自己的线程中,并且让该线程与“父级”对话一直是一场噩梦。从服务的角度来看,这里是布局:

Endpoint (svc - inherits from BaseServerHost, mentioned above)
  - Business Layer
    - Data Layer

当我在业务层的后台线程上捕获异常时,我将其冒泡到 Endpoint 实例(继承自 BaseServerHost),然后尝试为该特定服务触发 BaseServerHost 的 UnhandledException 事件(由实例化的拥有的 ServiceManager 附加它)。不幸的是,事件处理程序不再存在,所以它什么都不做。我已经尝试了很多事情来让它发挥作用,到目前为止,我所有的努力都是徒劳的。

查看完整模型时(如下所示),我需要让业务层了解其父端点(这可行),并且端点需要了解正在运行的 BaseServerHost 实例,该实例需要了解托管它的 ServiceManager 所以在我们的标准日志记录程序中,可以将错误冒泡到此。

Host
 - ServerManager
      - ServiceManager <=====================
           - BaseServerHost                ||
                - Endpoint (svc)           ||
                     - Business Layer <======
                          - Data Layer

我尝试了静态类但没有运气,甚至将 ServerManager 设为静态并公开其先前的内部 ServiceManager 集合(因此它们可以被关闭),但该集合也始终为空或 null。

对制作这项工作的想法?

编辑:在进一步挖掘之后,我找到了一个关于我如何设想这个工作的例子。在标准的 ASP.NET 网站中,在任何页面/处理程序等上,您都可以使用 HttpContext.Current 属性来访问该请求的当前上下文。这正是我希望它与返回该服务的拥有 ServiceManager 的“ServiceManager.Current”一起使用的方式。也许这有帮助?

4

1 回答 1

0

也许你应该考虑用 CallContext 做点什么:

http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx

您可以使用 SetData/GetData 或 LogicalSetData/LogicalGetData,具体取决于您是否希望 ServiceManager 与一个物理线程 (SetData) 或“逻辑”线程 (LogicalSetData) 相关联。使用 LogicalSetData,您可以在线程以及该线程的“子”线程中使相同的 ServiceManager 实例可用。当我找到它们时,我会尝试发布一些可能有用的链接。

这是codeproject 上“虚拟单例模式”的链接。

这是“线程单例”的链接

这是“环境上下文”的链接

所有这些想法都是相似的。本质上,您有一个具有静态 Current 属性的对象(可以是 get 或 get/set)。Current 使用 SetData(将“Current”值与当前线程关联)或 LogicalSetData(将“Current”值与当前线程关联并将值流向任何“子”线程)。

HttpContext 以类似的方式实现。

System.Diagnostics.CorrelationManager是另一个以类似方式实现的好例子。

我认为 Ambient Context 文章很好地解释了你可以用这个想法完成什么。

每当我讨论 CallContext 时,我都会尝试在Jeffrey Richter 的博客中包含此条目的链接。

最终,我不确定这是否会对您有所帮助。如果您有一个多线程服务器应用程序(也许每个请求都由一个线程完成,并且多个请求可以在不同线程上同时完成),那么它会很有用,您可能每个线程都有一个 ServiceManager。在这种情况下,您可以在 ServiceManager 上使用静态 Current 方法,该方法始终为特定线程返回正确的 ServiceManager 实例,因为它将 ServiceManager 存储在 CallContext 中。像这样的东西:

public class ServiceManager
{
  static string serviceManagerSlot = "ServiceManager";

  public static ServiceManager Current
  {
    get
    {
      ServiceManager sm = null;
      object o = CallContext.GetData(serviceManagerSlot);
      if (o == null)
      {
        o = new ServiceManager();
        CallContext.SetData(serviceManagerSlot, o);
      }
      sm = (ServiceManager)o;
      return sm;
    }

    set
    {
      CallContext.SetData(serviceManagerSlot, value);
    }
  }
}

在您的流程的早期,您可能会配置一个 ServiceManager 以在当前线程(或当前“逻辑”线程)中使用,然后存储在“当前”属性中:

ServiceManager sm = new ServiceManager(thread specific properties?);
ServiceManager.Current = sm;

现在,每当您在代码中检索 ServiceManager.Current 时,它将是您当前正在执行的线程的正确 ServiceManager。

这整个想法可能并不是你想要的。

从您的评论中,您说您在发生异常时尝试检索的 CallContext 数据为空。这可能意味着异常在与设置 CallContext 数据的线程不同的线程上引发和/或捕获。您可以尝试使用 LogicalSetData 来查看是否有帮助。

正如我所说,我不知道这是否会对您有所帮助,但希望我已经足够清楚(并且示例也足够清楚),因此您可以判断这些想法是否适用于您的情况。

祝你好运。

于 2011-02-08T23:09:40.217 回答