翻译《Enterprise Library Guide》异步数据访问
异步数据访问配置
企业库支持异步访问,在使用异步访问数据前,你必须设置所使用的连接字符串,默认情况下,数据库连接的异步特性是被禁用的,如果你想提高应用程序的性能,我们推荐你使用异步数据访问,你需要为配置文件中的连接连接字符串中新增Asynchronous Processing=true (or just async=true)。
<connectionStrings> <add name="AsyncExampleDatabase" connectionString="Asynchronous Processing=true; Data Source=. Initial Catalog=MyDatabase; Integrated Security=True;" providerName="System.Data.SqlClient" /> </connectionStrings>
另外需要注意,只有SQL Server数据库支持异步访问,Database也提供了一个名为SupportsAsync的属性用于检测当前数据库是否支持异步操作。异步操作通常传递给线程一个回调方法,最简单的方法是通过Lambda表达式指定一个回调处理程序,关于异步数据访问的注意点如下:
你可以使用System.Threading命名空间中提供的标准的方法创建线程,通过线程实现异步执行数据访问程序块的方法,你也可以通过Cancel方法取消一个正在执行的Command命令。
你可以引用并行库通过Task对象来实现异步操作。
通过异步的BeginExecuteReader并不会提供一个CommandBehavior参数,默认情况下,企业库已经在内部自动将CommandBehavior属性设置为CloseConnection,这样能够保证当DataReader被关闭时,对应的数据库连接也被关闭,从而释放资源。另外需要注意,如果为DataReader设置了指定的事务的话,CommandBehavior属性不会被设置为CloseConnection值。
请务必确保你调用相应的EndExecute方法,当您使用异步数据访问时,即使你实际上并不需要访问的结果,或调用Cancel方法。如果不这样做可能会导致内存泄漏和消耗更多的系统资源。
使用具有多个活动结果集的ADO.NET(MARS)功能异步数据访问可能会产生意想不到的行为,一般应避免。
异步代码是很难编写和调试的, 你应该尽可能在确实提高性能的情况下使用它。
以下示例通过两种方法来异步检索据。第一种方法采用传统方法,第二种方法使用最新的任务并行库。
使用BeginXXX和EndXXX方法异步查询数据
下面的代码展示如何通过异步方式从SQL Server数据库读取数据行,首先需要创建一个Command对象的实例,然后为Command对象添加查询参数,通过调用Database类提供方法的BeginExecuteReader进行异步处理,该方法传递Command对象,和一个Lambda表达式作为回调,用于在数据检索处理完成后执行,并传递AsyncState参数为空。
// Create command to execute stored procedure and add parameters. DbCommand cmd = asyncDB.GetStoredProcCommand("ListOrdersSlowly"); asyncDB.AddInParameter(cmd, "state", DbType.String, "Colorado"); asyncDB.AddInParameter(cmd, "status", DbType.String, "DRAFT");
<span style="color: green">// Execute the query asynchronously specifying the command and the
// expression to execute when the data access process completes.
</span>asyncDB.BeginExecuteReader(cmd, asyncResult =>
{
<span style="color: green">// Lambda expression executed when the data access completes.
</span><span style="color: blue">try
</span>{
<span style="color: blue">using </span>(<span style="color: #2b91af">IDataReader </span>reader = asyncDB.EndExecuteReader(asyncResult))
{
DisplayRowValues(reader);
}
}
<span style="color: blue">catch </span>(<span style="color: #2b91af">Exception </span>ex)
{
<span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Error after data access completed: {0}"</span>, ex.Message);
}
}, <span style="color: blue">null</span>);
在Windows窗体或WPF应用程序中,你不应该尝试直接从Lambda表达式中更新UI,因为Lambda表达式是在不同的线程上运行,你应该使用Invoke方法在WPF窗体或在Dispatcher对象。
使用并行库任务Task进行异步数据处理
在正式版的企业库中,Database类的异步方法本身不支持Task对象,然而,你可以使用TaskFactory.FromAsync方法将BeginXXX和EndXXX转换为Task,在下面的代码中,使用它执行一个存储过程,你可以知道如何通过FromAsync方法创建一个Task对象实例,并通过async和await关键字控制Task。
if (!SupportsAsync(asyncDB)) { return; DoReadDataAsynchronouslyTask().Wait(); }
<span style="color: blue">private static </span>async Task DoReadDataAsynchronouslyTask()
{
<span style="color: blue">try
</span>{
<span style="color: green">// Create command to execute stored procedure and add parameters
</span><span style="color: #2b91af">DbCommand </span>cmd = asyncDB.GetStoredProcCommand(<span style="color: #a31515">"ListOrdersSlowly"</span>);
asyncDB.AddInParameter(cmd, <span style="color: #a31515">"state"</span>, <span style="color: #2b91af">DbType</span>.String, <span style="color: #a31515">"Colorado"</span>);
asyncDB.AddInParameter(cmd, <span style="color: #a31515">"status"</span>, <span style="color: #2b91af">DbType</span>.String, <span style="color: #a31515">"DRAFT"</span>);
<span style="color: blue">using </span>(<span style="color: blue">var </span>timer = <span style="color: blue">new </span><span style="color: #2b91af">Timer</span>(_ => <span style="color: #2b91af">Console</span>.Write(<span style="color: #a31515">"Waiting... "</span>)))
{
timer.Change(0, 1000);
<span style="color: blue">using </span>(<span style="color: blue">var </span>reader = await Task<<span style="color: #2b91af">IDataReader</span>>.Factory
.FromAsync<<span style="color: #2b91af">DbCommand</span>>(asyncDB.BeginExecuteReader, asyncDB.EndExecuteReader, cmd, <span style="color: blue">null</span>))
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
<span style="color: #2b91af">Console</span>.WriteLine();
<span style="color: #2b91af">Console</span>.WriteLine();
DisplayRowValues(reader);
}
}
}
<span style="color: blue">catch </span>(<span style="color: #2b91af">Exception </span>ex)
{
<span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Error while starting data access: {0}"</span>, ex.Message);
}
}