博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
LINQ to SQL异步查询
阅读量:5931 次
发布时间:2019-06-19

本文共 6160 字,大约阅读时间需要 20 分钟。

异步操作是提高Web应用程序吞吐量的重要手段,关于这方面的话题已经在前文《 》中解释过了。对于大多数互联网应用来说,性能瓶颈数据库访问。换句话说,一个请求在数据库操作上所花的时间往往是最多的——并且占总时间的90%以上。因此,当Web应用程序的吞吐量因为数据库操作的阻塞而受到影响的话,我们可是尝试使用异步数据库操作来进行优化。
  如果我们使用LINQ to SQL,在默认情况下是无法实现异步查询的,所有的操作都非常自然——异步是不自然的,因为它把连续的操作拆成了两段。如果理解了《 》一文中所提出的扩展方法,使用LINQ to SQL实现数据库的异步查询的方法应该就很容易想到了:借助SqlCommand对象来完成。
  在.NET中实现一个此类异步操作自然是按照标准的APM(Asynchronous Programming Model,异步编程模型)来开发一对Begin和End方法。按照APM进行开发其实不是一件非常容易的事情,不过在.NET 2.0里,尤其是在.NET 3.5中的某个特性,开发此类功能就变得容易一些了——也就是说,这是个在.NET 1.x => .NET 2.0 => .NET 3.5的演变过程中都得到改进的特性,猜出来是什么了吗?没错,这个特性就是“匿名方法”。
  匿名方法事实上基于委托,有了匿名方法这个特性,在一些本该使用委托的地方就可以直接定义一个函数了。这种做法在很多时候能够减少相当程度的代码量,尤其是本来很难省去的一些“条条框框”。例如,我们现在需要对一个Article列表按评论数量进行排序,并且在排序时可以指定升序或降序。如果没有匿名方法,我们一般会这么做:
public  
void SortByCommentCount(
List<
Article> articleList, 
bool ascending)
{
    
// use the overloaded method: List<T>.Sort(Comparison<T> compare)
    
ArticleComparison comparison = 
new 
ArticleComparison(ascending);
    articleList.Sort(
new 
Comparison<
Article>(comparison.Compare));
}
 
class 
ArticleComparison
{
    
private 
bool m_ascending;
 
    
public ArticleComparison(
bool ascending)
    {
        
this.m_ascending = ascending;
    }
 
    
public 
int Compare(
Article a, 
Article b)
    {
        
return (a.CommentCount - b.CommentCount) * (
this.m_ascending ? 1 : -1);
    }
}
  我们使用接受Comparison<T>作为参数的List<T>.Sort方法重载,如果没有特别的要求,我们只需写一个静态方法就可以了——只要方法签名符合Comparision<Article>就行了。可惜在这里,我们需要写一个额外的类,因为我们需要访问一个额外的参数ascending,而这个参数不能在一个独立的Comparision<Article>委托中获得。于是我们写了一个ArticleComparison类,它唯一的目的就是封装ascending。如果我们每次使用Sort功能都要封装一个类的话编写的代码也就太多了。但是如果我们有了匿名方法之后:
public  
void SortByCommentCount(
List<
Article> articleList, 
bool ascending)
{
    articleList.Sort(
delegate(
Article a, 
Article b)
    {
        
return (a.CommentCount - b.CommentCount) * (ascending ? 1 : -1);
    });
}
  很明显,这种内联写法省去了额外的方法定义。而且更重要的是,匿名函数体内部能够访问到当前堆栈中的变量——其实这点才是最重要的。事实上,匿名方法的实现原理正是由编译器自动生成了一个封装类。有了匿名方法这个特性,我们就可以使用非常优雅的做法来实现一些轻量的委托。至于.NET 3.5里对于匿名方法的改进,主要在于引入了Lambda Expression:
public  
void SortByCommentCount(
List<
Article> articleList, 
bool ascending)
{
    articleList.Sort( (a, b) => (a.CommentCount - b.CommentCount) * (ascending ? 1 : -1));
}
  编译器会将现在的代码编译成之前与之前匿名方法相似的IL代码。.NET 3.5中LINQ的大量操作都以委托作为参数,因此也正是因为有了Lamda Expression到委托的转化,LINQ才能有如此威力。现在开发一个APM操作就方便多了。我们现在来构造一个扩展,将LINQ to SQL的查询异步化。首先是Begin方法(其中有些辅助方法以及参数的含义可以见之前的《 》一文):
public  
static 
IAsyncResult BeginExecuteQuery(
    
this 
DataContext dataContext, 
IQueryable query, 
bool withNoLock,
    
AsyncCallback callback, 
object asyncState)
{
    
SqlCommand command = dataContext.GetCommand(query, withNoLock);
    dataContext.OpenConnection();
 
    
AsyncResult<
DbDataReader> asyncResult = 
        new 
AsyncResult<
DbDataReader>(asyncState);
    command.BeginExecuteReader(ar =>
    {
        
try
        {
            asyncResult.Result = command.EndExecuteReader(ar);
        }
        
catch (
Exception e)
        {
            asyncResult.Exception = e;
        }
        
finally
        {
            asyncResult.Complete();
            
if (callback != 
null) callback(asyncResult);
        }
    }, 
null);
 
    
return asyncResult;
}
  在《 》一文中我们已经谈过什么样的异步操作是“有效”的,从文章的内容我们不难得出一个结论,那就是我们无法使用托管代码“自行”实现适合I/O-Bound Operation的异步操作。我们为DataContext扩展的异步操作肯定是“封装”了ADO.NET所提供的异步特性来完成。很显然,我们需要获得一个DbDataReader,因此我们调用会调用SqlCommand对象的BeginExecuteReader方法,该方法的第一个参数是一个AsyncCallback委托类型的对象,当数据库的异步查询完成之后即会调用该委托,在这里使用匿名方法更合适。
  这里的关键是用到了自己扩展的AsyncResult<T>类,该类除了标准的IAsyncResult实现之外,还释放出一个System.Exception类型的Exception属性和T类型的Result属性。这两个属性的作用可以从上面的代码中看出:Result的作用是保留异步操作的结果,Exception的作用自然是临时保存调用SqlCommand.EndExecuteReader方法之后捕获到的异常。这两个临时保留的对象都是为了在EndExecuteQuery方法中作进一步处理:
public  
static 
List<T> EndExecuteQuery<T>(
    this 
DataContext dataContext, 
IAsyncResult ar)
{
    
AsyncResult<
DbDataReader> asyncResult = (
AsyncResult<
DbDataReader>)ar;
    
if (!asyncResult.IsCompleted)
    {
        asyncResult.AsyncWaitHandle.WaitOne();
    }
 
    
if (asyncResult.Exception != 
null)
    {
        
throw asyncResult.Exception;
    }
 
    
using (
DbDataReader reader = asyncResult.Result)
    {
        
return dataContext.Translate<T>(reader).ToList();
    }
}
  根据APM的规则,End方法将接受一个参数,那就是Begin方法的返回值。因此我们可以在代码中将其转换成AsyncResult<DbDataReader>对象。按照规则,如果调用End方法时异步调用还没有完成,则应该阻塞当前线程直到异步操作完毕,因此我们的代码调用了AsyncWaitHandle的WaitOne方法——当然,这里的写法和我们的具体实现方式有关(详见下文中AsyncResult<T>的实现方法)。然后检查Exception属性,如果不为空则表明在执行数据库的异步操作时抛出了一场,因此我们的End方法也将其继续抛出。最后自然是根据获得的DbDataReader对象,并借助DataContext的Translate方法生成对象。
  至于AsyncResult<T>类型的实现方法非常简单,我在这里将其简单贴出,就不多作什么解释了。不过有一点显而易见,由于C# 3.0中的Automatic Property特性,代码量比之前又能少了许多:
private  
class 
AsyncResult<T> : 
IAsyncResult
{
    
public AsyncResult(
object asyncState)
    {
        
this.AsyncState = asyncState;
        
this.IsCompleted = 
false;
        
this.AsyncWaitHandle = 
new 
ManualResetEvent(
false);
    }
 
    
public 
object AsyncState { 
get
private 
set; }
 
    
public 
WaitHandle AsyncWaitHandle { 
get
private 
set; }
 
    
public 
bool CompletedSynchronously { 
get { 
return 
false; } }
 
    
public 
bool IsCompleted { 
get
private 
set; }
 
    
public 
void Complete()
    {
        
this.IsCompleted = 
true;
        (
this.AsyncWaitHandle 
as 
ManualResetEvent).Set();
    }
 
    
public T Result { 
get
set; }
 
    
public 
Exception Exception { 
get
set; }
}
  那么现在就来试用一下。在《 》中也提到过,即使异步操作得到了IOCP支持,也必须正确使用这些异步操作才能真正得到效果。换句话说,我们必须在ASP.NET提供的几个方面来使用异步功能。ASP.NET目前提供了三个可用于异步操作的地方:异步HttpModule,异步HttpHandler和异步Page,其中最常用的可能就是异步Page了。
public  
partial 
class 
AsyncPage : System.Web.UI.
Page
{
    
protected 
void Page_Load(
object sender, 
EventArgs e)
    {
        
this.AddOnPreRenderCompleteAsync(
            
new 
BeginEventHandler(BeginAsyncOperation),
            
new 
EndEventHandler(EndAsyncOperation) );
    }
 
    
private 
ItemDataContext m_dataContext;
 
    
private 
IAsyncResult BeginAsyncOperation(
object sender, 
EventArgs e,
        
AsyncCallback cb, 
object state)
    {
        
this.m_dataContext = 
new 
ItemDataContext(); 
        
var query = (
from item 
in 
this.m_dataContext.Items
                     
orderby item.ItemID
                     
select item).Skip(10).Take(10);
 
        
return 
this.m_dataContext.BeginExecuteQuery(query, cb, state);
    }
 
    
private 
void EndAsyncOperation(
IAsyncResult ar)
    {
        
using (
this.m_dataContext.Connection)
        {
            
this.rptItems.DataSource = 
this.m_dataContext.EndExecuteQuery(ar);
            
this.rptItems.DataBind();
        }
    }
}
  异步数据库访问已经变得非常容易了,即使是LINQ to SQL也能较轻松地地获得这方面的支持。不过在实际开发过程中我们可能还会遇到一点小问题:我们的应用程序是分层的,而异步数据库访问是数据访问层的能力。而如果我们要在表现层(HttpModule、HttpHandler、Page都属于表现层)使用异步方法,就需要让业务逻辑也提供一系列的支持——可能只是过渡,可能又是更多。这方面对于单线程的业务逻辑对象来说可能不是问题,但是在某些架构中,业务逻辑对象可能会被多个线程(请求)同时访问。但是既然要使用异步操作,就需要把一组Begin和End消息发送给同一个对象——在多线程共享一个业务逻辑对象的情况下,又该如何知道某个End消息应该转发给哪个下层对象呢?
  这个话题我们下次再讨论吧。
本文转自 jeffz 51CTO博客,原文链接:http://blog.51cto.com/jeffz/63763,如需转载请自行联系原作者
你可能感兴趣的文章
openstack学习笔记十一 Nova
查看>>
希捷带来限量版纪念硬盘
查看>>
Unix整理笔记-超级无敌常用命令杂谈1-里程碑M6
查看>>
Docker的安装与启动
查看>>
zabbix使用Omsa来监控Dell服务器的硬件信息
查看>>
【CSS】【8】CSS盒子距离计算
查看>>
采用移动模拟中继接入可能会遇到的问题
查看>>
Android异步加载图片
查看>>
通过在shell脚本中用expect实现远程scp文件
查看>>
php动态编译openssl过程
查看>>
文件查找之find用法
查看>>
Wireless在域里面实施WPA认证设定应用
查看>>
springmvc配置MappingJackson2HttpMessageConverter实现属性驼峰和下划线的转换
查看>>
web服务器time_wait值过高解决方案
查看>>
Linux下用户、组的命令及修改文件实现用户和组的添加
查看>>
IBM Server RAID 7K error code 2807
查看>>
Shell脚本中几种条件表达式的用法
查看>>
深入学习NAT工作原理
查看>>
Cisco 2960 3750交换机端口流量限速(QOS)
查看>>
【一天一个shell命令】文本操作系列-dd
查看>>