具有后台线程的 WCF NetTCP
背景:
我有一个系统,在具有 NetTCP 绑定的 Windows 服务内托管 WCF 服务。要将新服务添加到集合中,只需在
下面是如何初始化服务的示例:
Host
- ServerManager
- ServiceManager
- BaseServerHost
ServerManager 实例有一组 ServiceManager,每个 ServiceManager 都与一个服务实例相关,这是标准 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 附加到)。不幸的是,事件处理程序不再存在,因此它什么也不做。我已经尝试了很多方法来让它发挥作用,但到目前为止我所有的努力都是徒劳的。
当查看完整模型(如下所示)时,我需要让业务层了解其父端点(这是有效的),并且端点需要了解正在运行的 BaseServerHost 实例,该实例需要了解托管它的 ServiceManager,以便错误可以冒泡到此,以便在我们的标准日志记录程序中使用。
Host
- ServerManager
- ServiceManager <=====================
- BaseServerHost ||
- Endpoint (svc) ||
- Business Layer <======
- Data Layer
我尝试过静态类,但没有成功,甚至将 ServerManager 设为静态并公开其先前内部的 ServiceManager 集合(以便可以关闭它们),但该集合也始终为空或为 null。
有想法让这项工作成功吗?
编辑:进一步挖掘后,我找到了一个示例,准确地说明了我设想的工作方式。在标准 ASP.NET 网站中,在任何页面/处理程序等上,您可以使用 HttpContext.Current 属性来访问该请求的当前上下文。这正是我希望它与返回该服务所属 ServiceManager 的“ServiceManager.Current”一起工作的方式。也许这有帮助?
Background:
I have a system that hosts WCF services inside a Windows Service with NetTCP binding. To add a new service to the collection you simply add the standard WCF config entries inside <system.serviceModel -> services /> and then add a line inside a custom configuration section that tells the hosting framework it needs to initialize the service. Each service is initialized with its own background thread and AppDomain instance to keep everything isolated.
Here is an example of how the services are initialized:
Host
- ServerManager
- ServiceManager
- BaseServerHost
The ServerManager instance has a collection of ServiceManagers that each correlate to a single service instance which is where the standard WCF implementation lies (ServiceHost.Open/Close, etc). The ServiceManager instance instantiates (based on the config - it has the standard assembly/type definition) an instance of the service by use of the BaseServerHost base class (abstract). Every service must inherit from this for the framework to be able to use it. As part of the initialization process BaseServerHost exposes a couple of events, specifically an UnhandledException event that the owning ServiceManager attaches itself to. (This part is critical in relation to the question below.)
This entire process works exceptionally well for us (one instance is running 63 services) as I can bring someone on who doesn't know anything about WCF and they can create services very quickly.
Question:
The problem I have run into is with background threading. A majority of the exposed methods on our endpoints do a significant amount of activity after a standard insert/update/delete method call such as sending messages to other systems. To keep performance up (the front-end is web-based) we let the initial insert/update/delete method do its thing and then fire off a background thread to handle all the stuff an end-user doesn't need to wait for to complete. This option works great until something in that background thread goes unhandled and brings the entire Windows service down, which I understand is by design (and I'm OK with).
Based on all of my research I have found that there is no way to implement a global try/catch (minus using the hacked config of enabling 1.1 handling of background crashing) so my team will have to go back and get those in the appropriate places. That aside, what I've found is on the endpoint side of the WCF hosting appears to be in its own thread on each call and getting that thread to talk to the "parent" has been a nightmare. From the service viewpoint here is the layout:
Endpoint (svc - inherits from BaseServerHost, mentioned above)
- Business Layer
- Data Layer
When I catch an exception on a background thread in the business layer I bubble it up to the Endpoint instance (which inherits from BaseServerHost) which then attempts to fire BaseServerHost's UnhandledException event for this particular service (which was attached to by the owning ServiceManager that instantiated it). Unfortunately the event handler is no longer there so it does nothing at all. I've tried numerous things to get this to work and thus far all of my efforts have been in vain.
When looking at the full model (shown below), I need to make the Business layer know about its parent Endpoint (this works) and the endpoint needs to know about the running BaseServerHost instance which needs to know about the ServiceManager that is hosting it so the errors can be bubbled up to this for use in our standard logging procedures.
Host
- ServerManager
- ServiceManager <=====================
- BaseServerHost ||
- Endpoint (svc) ||
- Business Layer <======
- Data Layer
I've tried static classes with no luck and even went as far as making ServerManager static and expoting its previously internal collection of ServiceManagers (so they can be shutdown), but that collection is always empty or null too.
Thoughts on making this work?
EDIT: After digging a little further I found an example of exactly how I envision this working. In a standard ASP.NET website, on any page/handler etc. you can use the HttpContext.Current property to access the current context for that request. This is exactly how I would want this to work with a "ServiceManager.Current" returning the owning ServiceManager for that service. Perhaps that helps?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
也许您应该考虑使用 CallContext 做一些事情:
http ://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx
您可以使用 SetData/GetData 或 LogicalSetData/LogicalGetData,具体取决于您是否希望 ServiceManager与一个物理线程 (SetData) 或一个“逻辑”线程 (LogicalSetData) 相关联。使用 LogicalSetData,您可以在线程中以及该线程的“子”线程中使用相同的 ServiceManager 实例。稍后当我找到它们时,我会尝试发布一些可能有用的链接。
以下是 codeproject 上“虚拟单例模式” 的链接。
"Thread Singleton" 的链接
这是 ”的链接
指向“环境上下文 这些想法是相似的。本质上,您有一个具有静态 Current 属性(可以是 get 或 get/set)的对象。 Current 使用 SetData(将“Current”值与当前线程关联)或 LogicalSetData(将“Current”值与当前线程关联并将该值流到任何“子”线程)。
HttpContext 以类似的方式实现。
System.Diagnostics.CorrelationManager 是另一个很好的例子以类似的方式实施。
我认为 Ambient Context 文章很好地解释了这个想法可以实现什么。
每当我讨论 CallContext 时,我都会尝试将此链接包含到此 来自 Jeffrey Richter 博客的条目。
最后,我不确定这些内容是否会对您有所帮助。如果您有一个多线程服务器应用程序(也许每个请求都由一个线程完成,并且可以在不同线程上同时满足多个请求),那么它会很有用,每个线程可能有一个 ServiceManager。在这种情况下,您可以在 ServiceManager 上使用静态 Current 方法,该方法始终为特定线程返回正确的 ServiceManager 实例,因为它将 ServiceManager 存储在 CallContext 中。像这样的事情:
在流程的早期,您可以配置一个 ServiceManager 以在当前线程(或当前“逻辑”线程)中使用,然后存储在“Current”属性中:
现在,每当您在代码中检索 ServiceManager.Current 时,它将是您当前正在执行的线程的正确 ServiceManager。
这整个想法可能并不是您真正想要的。
根据您的评论,您说您在发生异常时尝试检索的 CallContext 数据为 null。这可能意味着异常是在与设置 CallContext 数据的线程不同的线程上引发和/或捕获的。您可以尝试使用 LogicalSetData 看看是否有帮助。
正如我所说,我不知道这些是否会对您有所帮助,但希望我已经足够清楚(并且示例也足够清楚),以便您可以判断这些想法是否适用于您的情况。
祝你好运。
Maybe you should look into doing something with CallContext:
http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx
You can use either SetData/GetData or LogicalSetData/LogicalGetData, depending on whether you want your ServiceManager to be associated with one physical thread (SetData) or a "logical" thread (LogicalSetData). With LogicalSetData you could make the same ServiceManager instance available within a thread as well as within that thread's "child" threads. Will try to post a couple of potentially useful links later when I can find them.
Here is a link to the "Virtual Singleton Pattern" on codeproject.
Here is a link to "Thread Singleton"
Here is a link to "Ambient Context"
All of these ideas are similar. Essentially, you have an object with a static Current property (can be get or get/set). Current puts its value in (and gets it from) the CallContext using either SetData (to associate the "Current" value with the current thread) or LogicalSetData (to associate the "Current" value with the current thread and to flow the value to any "child" threads).
HttpContext is implemented in a similar fashion.
System.Diagnostics.CorrelationManager is another good example that is implemented in a similar fashion.
I think the Ambient Context article does a pretty good job of explaining what you can accomplish with this idea.
Whenever I dicsuss CallContext, I try to also include this link to this entry from Jeffrey Richter's blog.
Ultimately, I'm not sure if any of this will help you or not. One it would be useful would be if you had a multithreaded server application (maybe each request is fulfilled by a thread and multiple requests can be fulfilled at the same time on different threads), you might have a ServiceManager per thread. In that case, you could have a static Current method on ServiceManager that would always return the correct ServiceManager instance for a particular thread because it stores the ServiceManager in the CallContext. Something like this:
Early in your process, you might configure a ServiceManager for use in the current thread (or current "logical" thread) and then store in the "Current" property:
Now, whenever you retrieve ServiceManager.Current in your code, it will be the correct ServiceManager for the thread in which you are current executing.
This whole idea might not really be what you want.
From your comment you say that the CallContext data that you try to retrieve in the event of an exception is null. That probably means that exception is being raised and/or caught on a different thread than the thread on which the CallContext data was set. You might try using LogicalSetData to see if that helps.
As I said, I don't know if any of this will help you, but hopefully I have been clear enough (and the examples have also been clear enough) so you can tell if these ideas apply to your situation or not.
Good luck.