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

2023/7/6 10:13:31

异步数据访问配置

企业库支持异步访问,在使用异步访问数据前,你必须设置所使用的连接字符串,默认情况下,数据库连接的异步特性是被禁用的,如果你想提高应用程序的性能,我们推荐你使用异步数据访问,你需要为配置文件中的连接连接字符串中新增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);
    }
}