赏梅斋

关注微软技术

博客园 首页 新随笔 联系 订阅 管理
  90 Posts :: 2 Stories :: 171 Comments :: 32 Trackbacks

许多开发人员都使用 Microsoft .NET Framework 缓存对象(例如 System.Web.Caching.Cache)帮助更好地利用内存并提高总体系统性能。但是,许多对象都不是“线程安全的”,缓存这些对象会导致应用程序失败,并导致意外或无关的用户错误。

 

缓存数据和对象

 

 

缓存是提高系统性能的一种很好的方法。但是,您必须根据线程安全性的需要权衡缓存的好处,因为有些 SharePoint 对象不是线程安全的,缓存会导致它们行为异常。

 

缓存非线程安全的 SharePoint 对象

您可能会尝试通过缓存从查询返回的 SPListItemCollection 对象来提高性能和内存利用率。一般来说,这是一种不错的做法;但是,SPListItemCollection 对象包含嵌入的 SPWeb 对象,后者不是线程安全的,不应该进行缓存。

例如,假定 SPListItemCollection 对象缓存在线程中。当其他线程尝试读取该对象时,应用程序会失败或行为异常,因为嵌入的 SPWeb 对象不是线程安全的。有关 SPWeb 对象和线程安全性的详细信息,请参阅 Microsoft.SharePoint.SPWeb 类。

下面一节中的指导介绍在多线程环境中缓存非线程安全的 SharePoint 对象时如何阻止出现问题。

 

了解线程同步的潜在缺点

您可能不知道代码在多线程环境中运行(默认情况下,Internet Information Services(即 IIS)是多线程的)或如何管理该环境。下面的示例演示有时用于缓存非线程安全的 Microsoft.SharePoint.SPListItemCollection 对象的代码。

不良的编码实践

缓存多个线程可能读取的对象

 

public void CacheData()
{
   SPListItemCollection oListItems;

   oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
   if(oListItems == null)
   {
      oListItems = DoQueryToReturnItems();
      Cache.Add("ListItemCacheName", oListItems, ..);
   }
}

 

 

上例中的缓存用法在功能上是正确的;不过,因为 ASP.NET 缓存对象是线程安全的,所以它引入了潜在的性能问题。如果上例中的查询需要 10 秒钟才能完成,则这段时间内可能会有许多用户同时尝试访问该页面。在这种情况下,所有用户会运行同一查询,这会尝试更新同一缓存对象。如果该同一查询运行 10 次、50 次或 100 次,并且多个线程尝试同时更新同一对象,尤其在多重处理的超线程计算机上,性能问题将变得尤其严重。

要防止多个查询同时访问相同对象,必须按如下所示更改代码。

应用锁

检查是否为空

 

private static object _lock =  new object();

public void CacheData()
{
   SPListItemCollection oListItems;

   lock(_lock)
   {
      oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
      if(oListItems == null)
      {
         oListItems = DoQueryToReturnItems();
         Cache.Add("ListItemCacheName", oListItems, ..);
     }
   }
}

 

 

可以通过将锁放在 if(oListItems == null) 代码块中来稍微提高性能。执行此操作时,无需挂起所有线程便可检查数据是否已缓存。根据查询返回数据所需的时间,仍可能有多个用户在同时运行查询。如果在多处理器计算机上运行,尤其会存在这种情况。请记住,运行的处理器越多,查询运行的时间越长,将锁放在 if() 代码块中引发问题的可能性就越大。要确保另一个线程没有在当前线程有机会进行处理之前创建 oListItems,可以使用以下模式。

应用锁

重新检查是否为空

 

private static object _lock =  new object();

public void CacheData()
{
   SPListItemCollection oListItems;
       oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
      if(oListItems == null)
      {
         lock (_lock)
         {
              // Ensure that the data was not loaded by a concurrent thread
              // while waiting for lock.
              oListItems = (SPListItemCollection)Cache[“ListItemCacheName”];
              if (oListItems == null)
              {
                   oListItems = DoQueryToReturnItems();
                   Cache.Add("ListItemCacheName", oListItems, ..);
              }
         }
     }
}

 

如果缓存已经填充,则上述示例的效果会与初始实现一样好。如果缓存尚未填充,并且系统的负载很轻,则获取锁会导致性能稍微下降。当系统的负载很重时,此方法应该能够显著提高性能,因为查询只执行一次而不是多次,并且与同步开销相比,查询的开销通常更昂贵。

这些示例中的代码会挂起 IIS 中运行的关键部分中的所有其他线程,并阻止其他线程访问缓存对象,直到已完全生成该缓存对象。这解决了线程同步问题;但是,代码仍然不正确,因为它缓存的是非线程安全的对象。

要解决线程安全性问题,可以缓存从 SPListItemCollection 对象创建的 DataTable 对象。您可以如下所示修改前面的示例,以便代码从 DataTable 对象获取数据。

良好的编码实践

缓存 DataTable 对象

 

 

private static object _lock =  new object();

public void CacheData()
{
   DataTable oDataTable;
   SPListItemCollection oListItems;
   lock(_lock)
   {
           oDataTable = (DataTable)Cache["ListItemCacheName"];
           if(oDataTable == null)
           {
              oListItems = DoQueryToReturnItems();
              oDataTable = oListItems.GetDataTable();
              Cache.Add("ListItemCacheName", oDataTable, ..);
           }
   }
}

 

 

posted on 2011-02-11 17:36 赏梅斋 阅读(...) 评论(...) 编辑 收藏