使用 Async/Await 任务方法与 SQL 查询 .NET 4.5
Microsoft .NET 4.5 引入了新的 "async 和 await" 方法,使用 .NET 任务对象轻松实现异步。
引言
Microsoft .NET 4.5 引入了新的 "async
和 await
" 方法,使用 .NET "Task" 对象轻松实现异步。
这使得开发人员可以更灵活地进行 async
调用,而不是使用传统的线程/回调方法。
在本文中,我构建了一个演示,展示如何将此应用于构建易于使用的 SQL 函数,这些函数可以 async
调用。
完整的演示和源代码可从本文获取。
Github
代码也已在 GitHub 上提供。欢迎您创建 issue 和 pull request。
入门
在本文中,我们将使用 .NET SQL Client 库连接到 Microsoft SQL Server 数据库,但是使用任务包装代码的相同方法可用于任何其他类型的连接。
在本例中,我们将把连接数据库以获取数据的代码包装到一个函数中,该函数根据所需的数据类型返回一个 "Task" 对象。
构建异步任务函数
我们首先构建返回任务对象的初始包装器。在本例中,我们将返回一个数据集。
题外话:任何数据类型都可以与任务对象一起返回,因此您可以返回 string
、整数或自定义类。
以下是我们函数的が基本包装器。这段代码将根据数据类型返回一个任务对象,并包含一个用于添加我们自己的代码来构建和返回数据集的部分。
public Task<DataSet> GetDataSetAsync(string sConnectionString, string sSQL)
{
return Task.Run(() =>
{
//YOUR CODE HERE
});
}
现在,我们可以将连接服务器以获取数据的代码插入到此函数中。
示例 1:根据 SQL 查询填充数据集的函数
在本例中,代码已添加到函数体中,该函数使用标准的 SQL 连接 string
和 System.Data.SQLClient
.NET 库中的对象来连接数据库并根据查询填充数据集。
数据集填充后,代码将使用标准的 "return
" 调用返回 dataset
。
public Task<DataSet> GetDataSetAsync
(string sConnectionString, string sSQL, params SqlParameter[] parameters)
{
return Task.Run(() =>
{
using (var newConnection = new SqlConnection(sConnectionString))
using (var mySQLAdapter = new SqlDataAdapter(sSQL, newConnection))
{
mySQLAdapter.SelectCommand.CommandType = CommandType.Text;
if (parameters != null) mySQLAdapter.SelectCommand.Parameters.AddRange(parameters);
DataSet myDataSet = new DataSet();
mySQLAdapter.Fill(myDataSet);
return myDataSet;
}
});
}
我们现在可以 异步调用此函数,以根据我们的查询返回一个数据集。
示例 2:执行命令并返回受影响行数的函数
在本例中,我们将数据类型更改为整数,因为我们只想返回此函数结果的受影响行数。
public Task<int> ExecuteAsync(string sConnectionString, string sSQL, params SqlParameter[] parameters)
{
return Task.Run(() =>
{
using (var newConnection = new SqlConnection(sConnectionString))
using (var newCommand = new SqlCommand(sSQL, newConnection))
{
newCommand.CommandType = CommandType.Text;
if (parameters != null) newCommand.Parameters.AddRange(parameters);
newConnection.Open();
return newCommand.ExecuteNonQuery();
}
});
}
此外,SqlCommand 对象上已存在异步任务函数,可用于进一步简化函数。
public async Task<int> ExecuteAsync(string sConnectionString,
string sSQL, params SqlParameter[] parameters)
{
using (var newConnection = new SqlConnection(sConnectionString))
using (var newCommand = new SqlCommand(sSQL, newConnection))
{
newCommand.CommandType = CommandType.Text;
if (parameters != null) newCommand.Parameters.AddRange(parameters);
await newConnection.OpenAsync().ConfigureAwait(false);
return await newCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
我们可以使用此函数执行 SQL 并获取受影响的行数。
调用异步任务函数
现在我们有了一个 async
函数来从 SQL 获取数据,我们可以使用 async
/await
子句从任何方法异步调用它。
要调用函数以获取数据,我们首先在方法声明中添加 "async
"。
private async <type> MyMethod()
现在我们的方法是一个 "async
" 方法,我们可以在从 SQL 函数返回的任务上使用 "await
" 选项。
执行任务函数
要执行任务函数,需要在函数调用前添加 "await
"。
这将执行函数,并将 "Result
" 返回到指定的变量(如果指定了)。
private async Task GetSomeData(string sSQL)
{
//Use Async method to get data
DataSet results = await GetDataSetAsync(sConnectionString, sSQL, sqlParams);
//Populate once data received
grdResults.DataSource = results.Tables[0];
}
变量不需要指定。您可以使用 "await
" 调用函数,这将运行任务而不检索结果。
private async Task ExecuteSomeData(string sSQL)
{
//Use Async method to get data
await ExecuteAsync(sConnectionString, sSQL, sqlParams);
}
如果您需要进行其他异步调用,可以多次指定 "await
" 选项。
您可以在 "await
" 命令之前和之后进行 GUI 更新,因为只有 await
命令是异步运行的。
使用 "async
/await
" 最简单的方法是使用 "await
" 选项,然后继续执行您的代码。
然而,.NET 任务对象在异步任务完成后提供了其他延续方法。
运行多个任务/链接任务
如果您需要运行多个 SQL 操作,有几种方法可以实现。
将任务指定为变量然后一起运行
您可以将多个命令指定为变量,然后异步地一次性运行它们,然后根据需要获取结果。
这可以通过将您的任务设置为变量来实现。
设置完成后,您需要将它们放入一个数组,然后使用 "Task.WhenAll
" 来异步运行所有任务。
完成后,您可以访问任务对象上的结果。
//Setup tasks
Task<int> ExecuteTask1 = database.ExecuteAsync(sConnectionString, sExecuteSQL1, sqlParams);
Task<int> ExecuteTask2 = database.ExecuteAsync(sConnectionString, sExecuteSQL2, sqlParams);
Task<int> ExecuteTask3 = database.ExecuteAsync(sConnectionString, sExecuteSQL3, sqlParams);
//Add to array
Task<int>[] Tasks = new Task<int>[] { ExecuteTask1, ExecuteTask2, ExecuteTask3 };
//Run all
await Task.WhenAll(Tasks);
//Get results
int iRowsAffected = Tasks[0].Result + Tasks[1].Result + Tasks[2].Result;
使用 "ContinueWith" 调用其他异步任务
链接任务的另一种方法是使用 SQL 函数返回的任务对象上的 "ContinueWith
" 方法。
在本例中,我们首先运行一个执行任务函数,然后一旦完成,我们运行一个选择任务函数以将数据集获取到变量中。
DataSet results = await database.ExecuteAsync(sConnectionString, sExecuteSQL, sqlParams).ContinueWith
(t => database.GetDataSetAsync(sConnectionString, sGetSQL, sqlParams)).Result;
使用 "ContinueWith" 调用标准方法
您可以在 "ContinueWith
" 方法上调用标准方法。
在本例中,我们使用任务对象的 "ContinueWith
" 将数据传递给一个单独的方法。
private async Task RunSQLQuery(string sSQL)
{
//Use await method to get data
await GetDataSetAsync
(sConnectionString, sSQL, sqlParams).ContinueWith(t => PopulateResults(t.Result));
}
private void PopulateResults(DataSet results)
{
//Do something with results
}
警告:以这种方式使用 "ContinueWith
" 时,它将继续 async
线程,这意味着如果您打算更新 GUI 组件(如 grid
或 textbox
控件),您可能会遇到错误:
跨线程操作无效:控件 MyControl 在非创建它的线程上被访问。
在这些情况下,您可以在 "ContinueWith
" 内部调用该方法,这将允许 GUI 更新。
await database.GetDataSetAsync(sConnectionString, sSQL, sqlParams).ContinueWith
(t => this.Invoke((Action)(() => { PopulateResultsToScreen(t.Result); })));
演示项目
本文中添加了一个简单的演示项目来演示 async
/await
选项。该应用程序将要求您输入服务器详细信息以连接到现有数据库。
您可以输入您的 SQL 查询,然后使用 "Execute" 按钮来执行 SQL 查询。
代码存储在 "frmTestForm
" 窗体中,如果您想查看此代码的实际运行情况,可以从 "Execute
" 方法开始。