使用设置为ConcurrencyMode.Multiple和I​​nstanceContextMode.PerCall的WCF服务行为属性时是否可能出现并发问题?

我们有一个WCF服务,可以进行大量的事务NHibernate调用。 偶尔我们会看到SQL超时,即使调用正在更新不同的行并且表被设置为行级锁定。

在深入研究日志之后,看起来不同的线程正在进入代码中的相同点(我们的事务使用块),并且更新挂起在提交上。 但是,这没有意义,因为我们认为以下服务类属性强制每个服务调用一个唯一的执行线程:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall)] 

我们最近将并发模式更改为ConcurrencyMode.Single并且还没有遇到任何问题,但是这个bug很难重现(如果有人想要刷出这样的bug,请告诉我!)。

无论如何,这一切都让我想到了问题:PerCall的InstanceContextMode是否应该在服务中强制执行线程安全,即使ConcurrencyMode设置为多个? 如何通过同一个服务实例为两个调用提供服务?

谢谢!

拥有两个不同的WCF客户端(即代理)引用WCF服务的同一实例的唯一方法是使用InstanceContextMode=InstanceContextMode.Single 。 如果缩放是一个问题,这是一个糟糕的选择,所以如果可以的话,你想要使用PerCall

使用PerCallWCF服务的每个CALL都会获得自己的WCF服务实例 。 没有共享服务实例,但这并不意味着它们不共享相同的后端存储(例如,数据库,内存,文件等)。 请记住, PerCall允许每个调用同时访问您的WCF服务。

ConcurrencyMode设置控制服务本身的线程模型。 设置为Single将所有WCF服务实例限制为在同一线程上运行。 因此,如果您同时连接多个客户端,则它们将仅在WCF服务端一次执行一个。 在这种情况下,您可以利用WCF提供同步。 正如您所见,它会正常工作,但我认为这只是对同步的宏级别控制 – 每个WCF服务调用将在下一次调用执行之前完整执行。

但是,将ConcurrencyMode设置为Multiple将允许所有WCF服务实例同时执行。 在这种情况下, 您负责提供必要的同步 。 可以将其视为对同步进行微级控制,因为您只能同步每个需要同步的调用的那些部分。

我希望我已经很好地解释了这一点,但这里是ConcurrencyMode的MSDN文档的片段,以防万一:

将ConcurrencyMode设置为Single指示系统一次将服务实例限制为一个执行线程,这使您无需处理线程问题。 值Multiple表示服务对象可以在任何时候由多个线程执行。 在这种情况下,您必须确保线程安全。

编辑

您询问

在使用ConcurrencyMode.Single时,使用PerCall与Single是否有任何性能提升? 或者是反过来的?

这可能取决于服务。

使用InstanceContextMode.PerCall ,可以通过代理为每个调用创建一个新的服务实例,因此您需要处理实例创建的开销。 假设您的服务构造函数没有做太多,这不会是一个问题。

使用InstanceContextMode.Single ,在应用程序的生命周期中只存在一个服务实例,因此实际上没有与实例创建相关的开销。 但是,此模式仅允许一个服务实例处理将要进行的每个调用。 因此,如果您同时进行多个呼叫,则每个呼叫都必须等待其他呼叫完成才能执行。

对于它的价值,这就是我如何做到这一点。 将PerCall实例上下文与Multiple并发一起使用。 在WCF服务类中,创建静态成员以便为您管理后端数据存储,然后根据需要使用lock语句, volatile字段等同步对这些静态成员的访问。这使您的服务可以在保持良好的同时进行维护线程安全。

我相信答案是,有多个线程(在客户端)使用相同的代理实例,因此可能允许多个调用进入同一个实例。 这篇文章有更详细的解释。

如果您不在服务器上使用双向回调, InstanceContextMode.PerCallConcurrencyMode.Single应该没问题。 在这种情况下,您将需要使用ConcurrencyMode.Reentrant或callback将无法访问锁定的服务实例并且将发生死锁。

由于每次调用服务实例创建,因此其他线程或调用无法访问它。 正如在其他答案文章中提到的文章中所述,如果会话是在绑定级别上创建并且您使用相同的服务代理对象,则此类组合仍然可能是一个问题。

因此,如果您不使用相同的代理对象或没有会话绑定,并且不使用双向回调到客户端(最有可能它们应该是OneWay) InstanceContextMode.PerCallConcurrencyMode.Single应该是好的。

我认为这完全取决于要求。 如果我们要多次调用相同的服务,那么我们可以使用InstanceContextMode是Single,并且concurrencymode是多个。