具有后台线程的WCF NetTCP

时间:2011-02-07 19:32:58

标签: multithreading error-handling wcf background-thread

背景:

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

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

Host
  - ServerManager
    - ServiceManager 
      - BaseServerHost

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

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

问题:

我遇到的问题是背景线程。在标准插入/更新/删除方法调用(例如向其他系统发送消息)之后,我们端点上的大多数公开方法都会执行大量活动。为了保持性能提升(前端是基于Web的)我们让初始插入/更新/删除方法做它的事情,然后触发后台线程来处理最终用户不需要等待的所有东西去完成。这个选项效果很好,直到后台线程中的某些内容未处理并将整个Windows服务关闭,我理解这是设计的(我很好)。

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

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

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

当查看完整模型(如下所示)时,我需要让Business层知道它的父Endpoint(这是有效的),并且端点需要知道正在运行的BaseServerHost实例,它需要知道ServiceManager是什么托管它,以便可以将错误冒出来,以便在我们的标准日志记录程序中使用。

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

我尝试过没有运气的静态类,甚至将ServerManager静态化并将其以前内部的ServiceManagers集合用于表示(因此可以关闭它们),但该集合始终为空或null。

关于做这项工作的想法?

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

1 个答案:

答案 0 :(得分: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上"Virtual Singleton Pattern"的链接。

以下是"Thread Singleton"

的链接

以下是"Ambient Context"

的链接

所有这些想法都是相似的。基本上,您有一个具有静态Current属性的对象(可以是get或get / set)。 Current使用SetData(将“Current”值与当前线程关联)或LogicalSetData(将“Current”值与当前线程关联起来并将值传递给任何值)将其值放入(并从中获取)CallContext “孩子”线程。

HttpContext以类似的方式实现。

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

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

每当我dicsuss CallContext时,我都会尝试将此链接包含在此entry from Jeffrey Richter's blog.

最终,我不确定这些是否会对你有所帮助。如果您有一个多线程服务器应用程序(可能每个请求都由一个线程完成,并且可以在不同的线程上同时完成多个请求),那么它可能会很有用,您可能每个线程都有一个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配置为在当前线程(或当前“逻辑”线程)中使用,然后存储在“Current”属性中:

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

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

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

根据您的评论,您说在异常情况下尝试检索的CallContext数据为null。这可能意味着异常是在与设置Call​​Context数据的线程不同的线程上引发和/或捕获的。您可以尝试使用LogicalSetData来查看是否有帮助。

正如我所说的,我不知道这些是否会对你有所帮助,但希望我已经足够清楚(而且这些例子已经足够清楚了)所以你可以判断这些想法是否适用于你的情况

祝你好运。