翻译《Enterprise Library Guide》异步数据访问

2023/7/6 02:13:31

"### 异步数据访问配置

企业库支持异步访问,在使用异步访问数据前,你必须设置所使用的连接字符串,默认情况下,数据库连接的异步特性是被禁用的,如果你想提高应用程序的性能,我们推荐你使用异步数据访问,你需要为配置文件中的连接连接字符串中新增Asynchronous Processing=true (or just async=true)。

<span style=color: blue><<span style=color: #a31515>connectionStrings<span style=color: blue>> <<span style=color: #a31515>add <span style=color: red>name<span style=color: blue>=<span style=color: blue>AsyncExampleDatabase <span style=color: red>connectionString<span style=color: blue>=<span style=color: blue>Asynchronous Processing=true; Data Source=. Initial Catalog=MyDatabase; Integrated Security=True; <span style=color: red>providerName<span style=color: blue>=<span style=color: blue>System.Data.SqlClient <span style=color: blue>/> </<span style=color: #a31515>connectionStrings<span style=color: blue>>

另外需要注意,只有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参数为空。

<span style=color: green>// Create command to execute stored procedure and add parameters. <span style=color: #2b91af>DbCommand cmd = asyncDB.GetStoredProcCommand(<span style=color: #a31515>ListOrdersSlowly); asyncDB.AddInParameter(cmd, <span style=color: #a31515>state, <span style=color: #2b91af>DbType.String, <span style=color: #a31515>Colorado); asyncDB.AddInParameter(cmd, <span style=color: #a31515>status, <span style=color: #2b91af>DbType.String, <span style=color: #a31515>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。

<span style=color: blue>if (!SupportsAsync(asyncDB)) { <span style=color: blue>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);
    }
}"