WCF服务方法中单例模式的问题

8

首先声明一下:我对WCF还比较新。

我正在开发一个服务器端程序,负责处理大量的业务逻辑。客户端可以通过WCF访问该程序。

我的主要WCF方法会调用其他几个私有方法。为了避免在每个私有方法中传递所有需要的“查找数据”,我决定使用一个名为DataProvider的类的单例实例来包含所有这些“查找数据”。

在程序结束时,我会“释放”DataProvider的查找数据,以便下次执行该程序时使用最新的查找数据。

以下是一个简化的示例:

 public void Generate()
 {
      try
      {
           //populate singleton DataProvider with it's lookup data...
           DataProvider.Instance.LoadLookupData();

           //do business logic...
      }
      finally
      {
           //release provider's lookup data...
           DataProvider.Release();
      }
 }

这个方法在同时(或接近)被两个不同的客户端执行时,会出现问题。这是因为它们共享同一个单例实例,先完成任务的客户端会在另一个客户端完成之前释放 DataProvider,从而导致问题。
那么,有哪些解决方案呢?我想避免传递所有查找数据,因此单例模式(或某种变体)似乎是一个不错的选择。我还需要支持多个客户端同时调用该方法。
我相信 WCF 服务已经配置成“Per-Call”模式。我不确定是否有一种方法可以配置 WCF 服务,使静态内存不在服务调用之间共享。
感谢您的帮助。

1
如果我理解有误,请有经验的人纠正我。但是,将查找数据传递到另一个类中不会创建所有数据的全新副本,对吗?它只是数据引用的副本,不是吗?(当然,除非它跨服务边界进行序列化,在这种情况下听起来不太可能) - Joel C
我想了解单例类的动机。它是您仅创建一次并且所有调用者都需要获取相同数据实例的服务吗?还是像返回调用者应该获取相同数据(会话)的服务? - Kangkan
1
“单例模式”的原因是业务逻辑需要创建大约30多种不同类型的对象,每个对象都需要访问查找数据。也许我有点懒,但我不想在30多个不同的构造函数中重复相同的参数。哈哈。我认为单例模式是最清晰的方法,以便每个对象可以轻松地访问查找数据。 - John Russell
3个回答

8
默认情况下,WCF使用“每个调用”(Per-Call)方式,这意味着为每个客户端调用创建新的WCF服务实例。现在,即使创建了WCF的新实例,由于您实现了单例模式,它仍然调用您的单例。
如果您想为每个调用创建一个查找(就像现在一样),则不应将其设置为单例。这样,调用您方法的每个客户端都会有一个新的查找实例,我想这正是您的意图。
但是,如果您拥有不那么频繁更改的查找,则建议在所有调用之间共享它,这将提高WCF服务的性能。您需要将WCF服务声明为:
InstanceContextMode = InstanceContextMode.Single
ConcurrencyMode = ConcurrencyMode.Multiple  

这是WCF自动为您创建单例的功能,因此您不必自己创建。其次,它将支持> 1个并发用户(ConcurrencyMode.Multiple)。
现在,如果您的查找表正在更改并且需要在一段时间后重新加载,则仍建议使用。
InstanceContextMode = InstanceContextMode.Single 
ConcurrencyMode = ConcurrencyMode.Multiple

你可以将它放入代码缓存中,并在特定时间或相对时间(1小时)后使其过期。

以下链接可能会对您有所帮助: 3种WCF实例管理方式(每次调用、每个会话和单一实例)

希望这能帮到你。


1
如果您正在使用在调用之间共享的缓存解决方案,为什么要使用单例实例模式呢?使用每次调用实例化和共享缓存解决方案似乎更简单,这样您就不必太担心线程安全问题。 - Joel C
@Joel C 无论您决定是按调用还是单例,您仍然需要考虑将数据加载到缓存中的同步问题。由于缓存将是唯一的实例,因此您需要进行同步。使用Per-Call将为每个客户端创建新的WCF服务实例,并在其后调用缓存数据。现在的问题是为什么需要创建新的WCF实例?这没有意义,这就是为什么我建议使用Single的原因。由于从缓存中读取日期,您不必担心同步,它是只读操作。 - Vlad Bezden
2
我更多地谈论的是如何处理WCF服务方法内的同步,因为ConcurrencyMode.Multiple意味着可以有多个线程同时执行相同的方法。对于每次调用,您每个请求只有一个实例,因此仅当您处理静态变量时,多线程和同步才会成为问题。 - Joel C
@Joel 绝对的,对于常规方法调用,我会使用 Per-Call 类型,但对于从 WCF 获取缓存数据的方法,我将使用 Single 和 ConcurencyMode.Multiple。这有意义吗? - Vlad Bezden
除了返回缓存数据之外,服务还有更多功能,注意部分 //执行业务逻辑... - Joel C
非常感谢您的输入。您的帖子引导我找到了最终的解决方案。我将WCF服务保留为Per Call。然而,对于“singleton”,我没有将其作为真正的静态singleton,而是使用此处描述的技术将其“附加”到Per Call OperationContext的扩展中:http://hyperthink.net/blog/a-simple-ish-approach-to-custom-context-in-wcf/。一旦WCF调用(业务逻辑)完成,上下文和我的singleton都会被删除。在多客户端场景中也非常有效。 - John Russell

4

WCF服务中的静态变量在实例之间始终共享,无论InstanceContextMode设置如何。似乎最好使用缓存模式来查找数据。这个缓存问题的答案提供了一些替代方案,尽管它们有点过时。

此外,如果您决定将整个服务实例作为单例(InstanceContextMode=Single),请注意,除非您还使代码多线程(ConcurrencyMode=Multiple),否则通常会破坏服务的可扩展性。如果您可以轻松编写线程安全的代码,则单例服务可能适合您。


1

最简单的方法是使用同步机制 - 你看过lock(...)吗?它会像Windows编程中的临界区一样充当门卫。

在你的类中定义一个静态对象。

例如:

static object lockObject = new object();

并在Generate方法中使用它

void Generate()
{
    lock(lockObject)
    {
    ...
    }
}

我假设您想在调用之间共享数据,或者这是每个被调用者的情况,即两个调用者获取不同的数据,但如果再次调用,则会获得相同的数据。 - Shaun Wilde
这肯定可以实现,但是业务逻辑需要几分钟的时间。如果另一个客户端已经在执行逻辑,我们不希望阻塞其他客户端执行逻辑。 - John Russell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接