使用 Task Parallel Library (TPL) 实现并行编程的详细指南
一. 引言
Task Parallel Library (TPL) 是 .NET 框架中的一组公共类型和 API,旨在简化并行性和并发性在应用程序中的实现。通过 TPL,开发人员可以更轻松地编写高效、可扩展且响应迅速的代码,特别是在需要处理多任务并行的场景下。
二. TPL 的关键特性
-
任务模型 (Task-based Programming Model)
TPL 引入了Task
概念,提供比传统线程更高层次的抽象,使开发者能够专注于任务的异步执行,而无需直接管理线程。 -
线程池管理 (Thread Pool Management)
TPL 内部使用线程池来管理线程的创建与复用,从而提高性能并减少手动创建新线程的开销。 -
自动负载均衡 (Automatic Load Balancing)
根据系统资源自动分配任务到可用线程上,无需开发者手动进行任务调度。 -
并行类 (Parallel Class)
TPL 提供的Parallel
类允许轻松并行执行循环,使循环中的各个迭代能够在多个处理器上同时运行。 -
任务连续性 (Task Continuations)
通过任务的连续性功能,开发者可以定义一个任务在另一个任务完成后执行,有助于简化复杂的异步工作流。 -
异常处理 (Exception Handling)
TPL 提供了强大的异常处理框架,任务中的异常会被自动捕获,并可以通过AggregateException
统一处理。
三. TPL 的优势
-
提高效率
通过复用线程池中的线程,减少了创建和销毁线程的开销,从而提高了应用程序的性能。 -
提升可扩展性
TPL 能够高效地管理和调度任务,使需要并行处理的应用程序具备更好的扩展性和性能。 -
简化代码
TPL 提供了更高级的并发处理抽象,使编写和维护异步代码变得更加简单。
四. 实例:简单的 TPL 任务执行
下面是一个简单的例子,通过 TPL 异步运行任务。
using System.Windows;
using System.Threading.Tasks; // 引入Task并行库
namespace TPL
{
public class TPLExample_1
{
public static void RunSimpleTask()
{
// 使用 Task.Run 创建并运行一个任务
Task task = Task.Run(() =>
{
// 弹出消息框,表明任务正在后台运行
MessageBox.Show("一个简单的任务正在后台运行。");
});
// 等待任务完成
task.Wait();
// 弹出消息框,表明任务已完成
MessageBox.Show("简单任务执行完毕。");
}
}
}
代码解释:
-
Task.Run
: 异步启动一个任务,在后台线程中执行。 -
task.Wait
: 等待任务执行完成。 -
MessageBox.Show
: 显示任务运行状态的提示消息。
五. 实例:复杂的 TPL 任务并行处理
在实际场景中,多个独立任务的并行执行是常见需求。例如,从多个数据库表中提取数据并处理结果,下面是一个复杂的 TPL 例子。
1. 数据库表结构
首先,我们定义两个示例 SQL SERVER 数据表:EmployeeDepartment
和 EmployeeDetails
。
CREATE TABLE EmployeeDepartment (
DepartmentID INT IDENTITY(100,1) PRIMARY KEY,
DepartmentName NVARCHAR(100)
);
CREATE TABLE EmployeeDetails (
EmployeeID INT PRIMARY KEY,
EmployeeName NVARCHAR(100),
DepartmentID INT,
FOREIGN KEY (DepartmentID) REFERENCES EmployeeDepartment(DepartmentID)
);
-- 插入示例数据
INSERT INTO EmployeeDepartment (DepartmentName) VALUES ('HR');
INSERT INTO EmployeeDepartment (DepartmentName) VALUES ('IT');
INSERT INTO EmployeeDepartment (DepartmentName) VALUES ('Finance');
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (1, 'Sanjay Kumar', 101);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (2, 'Aman Gupta', 102);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (3, 'Mariusz Postol', 101);
INSERT INTO EmployeeDetails (EmployeeID, EmployeeName, DepartmentID) VALUES (4, 'Atul Gupta', 100);
2. WPF 用户界面设计
接下来,我们构建一个简单的 WPF 用户界面,用于展示如何选择执行简单或复杂的任务。
<Window x:Class="TPL.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Orientation="Vertical" Margin="20,50,0,0">
<Button x:Name="SimpleTaskExample"
Content="简单任务示例"
Height="40"
Click="SimpleTaskExample_Click"
Margin="20"/>
<Button x:Name="ComplexTaskExample"
Content="复杂任务示例"
Height="40"
Click="ComplexTaskExample_Click"
Margin="20"/>
</StackPanel>
</Grid>
</Window>
3. 代码实现
在 MainWindow.xaml.cs
中处理按钮点击事件,并调用相关的 TPL 代码。
using System.Windows;
namespace TPL
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// 简单任务按钮点击事件
private void SimpleTaskExample_Click(object sender, RoutedEventArgs e)
{
TPLExample_1.RunSimpleTask(); // 执行简单任务
}
// 复杂任务按钮点击事件
private async void ComplexTaskExample_Click(object sender, RoutedEventArgs e)
{
string returnResult = await TPLComplexExample_2.RunTPLComplexExample(); // 执行复杂任务并获取结果
MessageBox.Show(returnResult, "执行结果");
}
}
}
4. 复杂任务的实现
定义复杂的任务处理逻辑,使用异步方法从不同的数据库表中提取数据,并处理结果。
using System.Data.SqlClient;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class TPLComplexExample_2
{
private static string sqlConString = "Server=你的服务器;Database=demo;User Id=sa;Password=你的密码;"; // 数据库连接字符串
public static async Task<string> RunTPLComplexExample()
{
string taskReturnresult = "";
List<List<Dictionary<string, object>>> databaseResult = null;
// 定义要提取数据的数据源表集合
var dataSources = new List<string> { "EmployeeDetails", "EmployeeDepartment" };
// 并行获取每个表的数据
var fetchTasks = dataSources.Select(source => Task.Run(() => ExtractingDataFromVariousTables(source))).ToArray();
try
{
// 等待所有数据提取任务完成
await Task.WhenAll(fetchTasks)
.ContinueWith(async InformationResults =>
{
// 合并提取结果
if (InformationResults.IsCompletedSuccessfully)
{
databaseResult = InformationResults.Result.ToList();
}
else if (InformationResults.IsFaulted)
{
taskReturnresult = InformationResults.Exception?.GetBaseException().Message
?? "获取数据时发生未知错误。";
}
})
.ContinueWith(async processingDataResults =>
{
if (processingDataResults.IsCompletedSuccessfully)
{
taskReturnresult = await ShowMergingResultsOfDataFromDifferentTables(databaseResult);
}
else if (processingDataResults.IsFaulted)
{
taskReturnresult = processingDataResults.Exception?.GetBaseException().Message
?? "合并数据时发生未知错误。";
}
});
}
catch (Exception ex)
{
MessageBox.Show("程序执行中发生错误:" + ex.Message);
}
return taskReturnresult;
}
// 从指定表中提取数据,使用 ADO.NET
private static async Task<List<Dictionary<string, object>>> ExtractingDataFromVariousTables(string tableName)
{
var data = new List<Dictionary<string, object>>();
try
{
using (SqlConnection connection = new SqlConnection(sqlConString))
{
string query = $"SELECT * FROM {tableName}";
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
row[reader.GetName(i)] = reader.GetValue(i);
}
data.Add(row);
}
}
}
}
catch (Exception ex)
{
return null; // 发生错误时返回 null
}
return data;
}
// 合并不同表的数据,显示结果
private static async Task<string> ShowMergingResultsOfDataFromDifferentTables(List<List<Dictionary<string, object>>> results)
{
try
{
if (results == null)
{
return "从数据库获取信息时发生错误。";
}
var employees = results[0];
var departments = results[1];
// 通过部门 ID 将员工与对应的部门合并
var mergedData = from emp in employees
join dept in departments
on emp["DepartmentID"] equals dept["DepartmentID"]
select new
{
EmployeeName = emp["EmployeeName"],
DepartmentName = dept["DepartmentName"]
};
// 返回合并后的结果字符串
string resultString = string.Join(", ", mergedData.Select(m => $"{m.EmployeeName} - {m.DepartmentName}"));
return resultString;
}
catch (Exception ex)
{
return "合并数据时发生错误。";
}
}
}
代码解释:
-
ExtractingDataFromVariousTables
: 从数据库表中提取数据。 -
Task.WhenAll
: 等待所有任务完成。 -
ShowMergingResultsOfDataFromDifferentTables
: 合并多个表的数据,显示结果。
六. 总结
通过 TPL,我们可以有效地处理并行任务,简化代码编写并提高性能。通过 Task
和 Parallel
类,我们能够更灵活地管理并发操作,特别是在需要处理多个独立任务时。