每周源代码51 - 异步数据库访问及LINQ to SQL 乐趣
[原文发表地址] The Weekly Source Code 51 - Asynchronous Database Access and LINQ to SQL Fun
[原文发表时间] 2010-03-02 12:24
通过阅读他人的源代码你可以学到很多东西。那就是这个系列背后的理念——“每周源代码”。通过编写代码你当然可以成为一个更优秀的程序员,但我认为优秀的程序员是通过尽可能多的阅读来变得更加优秀的。
我正在WebFormsMVP项目代码里进行探索,并发现了一个有趣的模式。
你已经见过了可以从数据库里获取数据并把它作为一个对象来提取的代码,就像这个:
public Widget Find(int id)
{
Widget widget = null;
widget = (from w in _db.Widgets
where w.Id == id
select w).SingleOrDefault();
return widget;
}
此代码是同步的,也就是说基本上它是会发生在同一个线程上,我们会等着,直到它完成。这里有一个同样代码的非同步版本。它是新(LINQ,在这种情况下,LINQ to SQL)和之前(DataReaders,等)版本的一个很好的组合。LINQ (to SQL) 查询正在查询,然后他们调用GetCommand来为这个查询获取底层SqlCommand。接着,它们调用SqlCommand上的BeginExecuteReader用以启动那个命令的异步执行。
SqlCommand _beginFindCmd = null;
public IAsyncResult BeginFind(int id, AsyncCallback callback, Object asyncState)
{
var query = from w in _db.Widgets
where w.Id == id
select w;
_beginFindCmd = _db.GetCommand(query) as SqlCommand;
_db.Connection.Open();
return _beginFindCmd.BeginExecuteReader(callback, asyncState, System.Data.CommandBehavior.CloseConnection);
}
public Widget EndFind(IAsyncResult result)
{
var rdr = _beginFindCmd.EndExecuteReader(result);
var widget = (from w in _db.Translate<Widget>(rdr)
select w).SingleOrDefault();
rdr.Close();
return widget;
}
在这个例子中,当这步完成了,EndFind会被调用而且它们会调用DataContext.Translate<T>传递它们所需要的类型(Widget)和源,以及取自EndExecuteReader的DataReader。这是一个异步的LINQ to SQL 调用。
我发现它很聪明,因此我给我的同行朋友和专家Stephen Toub发电子邮件,咨询他这是否符合以下任何一项或所有这几项:
a. 聪明的
b. 必须的
c. 更好地与PFX/TPL使用 (.NET Framework/Task Parallel Library平行扩展)
Stephen以他那一向正经的风格说到:
a) 把一个LINQ 查询转换成一个将被执行的命令并在如何执行上施加更多的控制是一个标准的方法。这就是说,我没有看到它做所有的东西,因此从那个能力方面来说它是聪明的。
b)异步地运行查询是有必要的;否则的话,调用计数器上的MoveNext将会阻塞。并且如果ADO.NET的MARS支持被运用的话( 多个异步结果集) ,你就可以使多个未完成的操作进行运作。
c) TPL并不能改善与SQL Server的交互,比如BeginExecuteReader仍然需要被调用。然而,TPL可被用于封装调用,这样你就可以获得一个Task<Widget>,这将会是一个更好使用的API。一旦你把它用作一个Task,你就可以做有用的事情如等待它,当它完成时为它调度工作,当多个操作被完成时等待多个操作或调度工作,等等。
另一个有趣的东西是WebFormsMVP 项目的PageAsyncTaskManagerWrapper:
namespace WebFormsMvp.Web
{
/// <summary>
/// Represents a class that wraps the page's async task methods
/// </summary>
public class PageAsyncTaskManagerWrapper : IAsyncTaskManager
{
readonly Page page;
/// <summary />
public PageAsyncTaskManagerWrapper(Page page)
{
this.page = page;
}
/// <summary>
/// Starts the execution of an asynchronous task.
/// </summary>
public void ExecuteRegisteredAsyncTasks()
{
page.ExecuteRegisteredAsyncTasks();
}
/// <summary>
/// Registers a new asynchronous task with the page.
/// </summary>
/// <param name="beginHandler">The handler to call when beginning an asynchronous task.</param>
/// <param name="endHandler">The handler to call when the task is completed successfully within the time-out period.</param>
/// <param name="timeout">The handler to call when the task is not completed successfully within the time-out period.</param>
/// <param name="state">The object that represents the state of the task.</param>
/// <param name="executeInParallel">The vlaue that indicates whether the task can be executed in parallel with other tasks.</param>
public void RegisterAsyncTask(BeginEventHandler beginHandler, EndEventHandler endHandler, EndEventHandler timeout, object state, bool executeInParallel)
{
page.RegisterAsyncTask(new PageAsyncTask(beginHandler, endHandler, timeout, state, executeInParallel));
}
}
}
它们为这些现有的System.Web.UI.Page方法做了一个很好的封装,他们像这样使用它,与先前异步的LINQ to SQL结合在一起:
AsyncManager.RegisterAsyncTask(
(asyncSender, ea, callback, state) => // Begin
{
return widgetRepository.BeginFindByName(e.Name, callback, state);
},
result => // End
{
var widget = widgetRepository.EndFindByName(result);
if (widget != null)
{
View.Model.Widgets.Add(widget);
}
},
result => { } // Timeout
, null, false);
AsyncManager.ExecuteRegisteredAsyncTasks();
它们一口气发出了它们的任务,然后这个任务异步地进行它的数据库工作,最后都汇聚到了一起。
我会剩下(也就是当前)对APIs的封装,以便留给读者返回一个Task<TResult>以作为练习,但是能够看到这个模式能否受益于Task Parallel Library将会很棒