循序渐进:学习使用 C# 和 .NET 进行并行编程(2024 年版)🧠
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
过渡到并行编程可以显著提升应用程序的性能。借助 C# 和 .NET,您可以利用强大的工具编写高效且可扩展的代码。让我们一步步深入探讨如何运用这些技术掌握并行编程。
并行编程入门
在本节中,我们将讨论什么是并行编程,为什么它很重要,以及为什么 C# 和 .NET 非常适合深入研究这项强大的技术。
什么是并行编程?
并行编程是一种计算架构,它允许多个进程同时运行。这种方法可以利用多核处理器显著加快计算速度。
并行编程在现代应用中的重要性
如今,人们对速度更快、效率更高的应用程序的需求日益增长。无论您从事数据处理、游戏开发还是 Web 应用程序开发,并发运行任务的能力都可能带来颠覆性的改变。
使用 C# 和 .NET 进行并行编程的优势
在并行编程方面,C# 和 .NET 具有诸多优势,使其成为开发人员的首选。让我们深入了解一下这些技术的独特之处。
简化的语法和结构
使用 C# 和 .NET 进行并行编程最显著的优势之一是它们提供的简化语法和结构。随着任务并行库 (TPL) 和async/await关键字的引入,编写并行和异步代码几乎与编写同步代码一样简单。这至关重要:
-
易用性:利用 TPL 和
async/await关键字可以减少管理线程和处理复杂同步机制所需的样板代码。 -
可读性:代码保持简洁、易读且易于维护。以下是一个简单的示例:
async Task<string> FetchDataAsync()
{
await Task.Delay(2000); // Simulates a 2-second delay
return "Data fetched!";
}
async这段代码片段展示了使用and运行异步操作是多么容易await。
卓越的工具支持
C# 和 .NET 都拥有出色的工具支持,可以简化开发流程。Visual Studio 和 Visual Studio Code 都提供以下功能:
-
IntelliSense:自动完成建议和参数信息,加快编码速度并减少错误。
-
调试工具:功能强大的调试工具,可以处理并行和异步代码,帮助您有效地诊断问题。
-
分析和诊断:内置性能分析器,用于分析并行代码的效率。
在开发复杂的高性能应用程序时,这些工具可以大大简化您的工作。
强大的社区和文档
对于任何开发者来说,无论你是新手还是专家,一个强大的社区和全面的文档都是非常宝贵的。
-
社区支持:C# 和 .NET 社区庞大且活跃。您可以在这里找到无数的教程、论坛和讨论区,寻求帮助、分享知识并了解最新趋势。
-
丰富的文档:微软为 C# 和 .NET 提供了详尽的文档,涵盖从基本语法到高级并行编程技术的所有内容。这些资源可以指导您解决可能遇到的任何挑战。
额外福利
除了以上几点之外,还有其他一些好处值得考虑:
-
跨平台功能:借助 .NET Core 和 .NET 5+,您的并行应用程序可以在多个操作系统上运行,包括 Windows、macOS 和 Linux。
-
性能优化:.NET 运行时包含针对并行和异步操作的各种性能优化,充分利用了现代多核处理器。
-
安全性:.NET 提供强大的安全功能,可帮助您编写安全的并行和异步代码,缓解常见的多线程问题,如竞态条件和死锁。
通过利用 C# 和 .NET 提供的这些功能和支持性生态系统,您可以更高效地开发强大、高性能的并行应用程序。
C#并行编程入门
接下来我们要动手实践了。我们将搭建开发环境,了解一些基本概念,并编写我们的第一个并行化“Hello World”程序。
搭建开发环境
首先,让我们来准备所需的工具。如果您还没有安装 Visual Studio 或 Visual Studio Code 以及 .NET SDK,请先安装它们。这些工具将是您探索并行编程的平台。
了解 .NET 任务并行库 (TPL)
任务并行库 (TPL) 是一组位于System.Threading.Tasks命名空间中的公共类型和 API。它是 .NET 并行编程的基石。它抽象了并行化任务过程中涉及的大部分复杂性。你可以把它想象成一根能让你的应用程序运行更快的魔法棒。
Hello World:你的第一个并行程序
理论就讲到这里,让我们直接来看代码吧!下面是一个简单的示例,演示如何使用并行运行“Hello World”程序Task。
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Task.Run(() => Console.WriteLine("Hello from Task!")).Wait();
Console.WriteLine("Hello from Main!");
}
}
Task运行这段代码,你会发现,当程序完成时,罐子会弹出消息Task,这展示了并行执行的一个简单而强大的示例。
深入了解任务并行库 (TPL)
让我们深入探讨一下。我们将探索如何在 C# 中创建任务、管理任务状态以及使用延续。
任务类别:创建和管理并行任务
该类Task是 TPL 的基础。您可以使用它来创建和启动任务。
Task myTask = Task.Run(() =>
{
// your code here
Console.WriteLine("Doing some work...");
});
myTask.Wait();
在这个代码片段中,我们创建了一个任务,该任务会打印一条消息,然后等待其完成。
任务执行和状态
任务可以有不同的状态,例如已完成Running、未Completed完成、Faulted未完成等等。您可以通过属性检查任务状态.Status。
if (myTask.Status == TaskStatus.RanToCompletion)
{
Console.WriteLine("Task finished successfully.");
}
这些状态有助于更有效地管理任务生命周期。
延续和基于任务的异步模式(TAP)
延续功能允许任务串联起来,这意味着一个任务可以在一个任务完成后开始。
Task firstTask = Task.Run(() => Console.WriteLine("First Task"));
Task continuation = firstTask.ContinueWith(t => Console.WriteLine("Continuation Task"));
continuation.Wait();
这种链式机制对于更复杂的并行工作流程至关重要。
使用并行类实现数据并行
让我们来探索一下这个Parallel类,它提供了并行循环和集合处理的方法。
平行课程简介
这个Parallel类简化了并行运行循环的过程。它就像给你的循环打了兴奋剂;它可以同时运行多个迭代。
使用 Parallel 进行迭代。
以下是一个使用Parallel.For迭代器遍历一系列数字的示例。
Parallel.For(0, 10, i =>
{
Console.WriteLine($"Processing number: {i}");
});
在这个循环中,每次迭代都是并行运行的,与常规循环相比,处理速度显著加快。
使用 Parallel.ForEach 处理集合
Parallel.ForEach工作原理类似,但用于集合。
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
Parallel.ForEach(numbers, number =>
{
Console.WriteLine($"Processing number: {number}");
});
利用这种方法,可以对任何可枚举集合进行并行处理。
并行循环中的异常处理
在使用并行循环时,务必注意异常处理。
try
{
Parallel.ForEach(numbers, number =>
{
if(number == 3)
{
throw new InvalidOperationException("Number 3 is not allowed!");
}
Console.WriteLine($"Processing number: {number}");
});
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
通过捕获异常AggregateException,您可以处理并行执行过程中抛出的多个异常。
高级并行编程技术
接下来我们将介绍一些高级主题,例如任务取消、组合子和async/await关键词的利用。
任务取消和超时管理
在特定情况下,您可能需要取消正在运行的任务,这时任务取消令牌就派上用场了。
CancellationTokenSource cts = new CancellationTokenSource();
Task longRunningTask = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i}");
Thread.Sleep(1000); // Simulate work
}
}, cts.Token);
Thread.Sleep(3000); // Let the task run for a bit
cts.Cancel();
try
{
longRunningTask.Wait();
}
catch (AggregateException ae)
{
Console.WriteLine("Task was cancelled.");
}
这段代码演示了如何使用 . 取消长时间运行的任务CancellationToken。
任务组合器:Task.WhenAll 和 Task.WhenAny
任务组合器有助于控制多个任务的执行流程。
Task task1 = Task.Delay(2000);
Task task2 = Task.Delay(1000);
Task.WhenAll(task1, task2).ContinueWith(_ => Console.WriteLine("Both tasks completed"));
Task.WhenAny(task1, task2).ContinueWith(t => Console.WriteLine("A task completed"));
这些组合器会等待所有或任何任务完成,然后执行后续函数。
并行编程中的异步/等待
这种async/await模式简化了异步代码的编写。
async Task ProcessDataAsync()
{
await Task.Run(() =>
{
// Simulated async workload
Thread.Sleep(2000);
Console.WriteLine("Data processed.");
});
}
await ProcessDataAsync();
Console.WriteLine("Processing done.");
这段代码ProcessDataAsync异步运行,等待其完成,不会阻塞主线程。
并行编程设计模式
设计模式为常见问题提供了行之有效的解决方案。让我们看看它们如何应用于并行编程。
数据并行设计模式
在数据并行中,操作是同时对不同的分布式数据片段执行的。模式示例包括:
-
MapReduce
-
数据分区
任务并行设计模式
任务并行是指同时运行不同的任务。例如:
-
分而治之
-
管道模式
了解 PLINQ(并行 LINQ)
PLINQ 代表并行LINQ,它允许并行查询数据。
var data = Enumerable.Range(1, 100).ToList();
var parallelQuery = data.AsParallel().Where(x => x % 2 == 0).ToList();
parallelQuery.ForEach(Console.WriteLine);
通过将 LINQ 查询转换为并行查询,可以更高效地处理数据集合。
优化并行代码
让我们来看一些优化并行代码和避免常见错误的技巧。
提高并行代码性能的技巧
-
使用分区器可以更好地进行负载均衡。
-
避免过度并行;任务过多反而会适得其反。
-
尽量减少共享状态,以避免争用问题。
避免并行编程中的常见陷阱
注意避免竞争条件、死锁和线程饥饿。这些问题会导致并行代码行为异常,甚至崩溃。
并行代码的性能分析与调试
使用 Visual Studio 的性能分析器和并发可视化工具等工具来分析并行代码的性能和行为。
C#并行编程的实际应用
并行编程在许多实际应用中都能带来颠覆性的变革。让我们一起来探讨它在高性能计算、可扩展Web应用程序和游戏开发中的应用,并通过示例和解释,帮助您更好地理解其影响。
高性能计算的并行编程
高性能计算 (HPC) 通常与需要大量计算能力的任务相关。这些任务可以从并行编程中获益匪浅,并行编程可以缩短计算时间并提高性能。
现实生活中的例子:天气预报
天气预报涉及处理海量数据集以预测天气模式。并行编程可以加快数据处理和模拟任务的速度。
using System;
using System.Threading.Tasks;
namespace WeatherSimulation
{
class Program
{
static void Main(string[] args)
{
int gridSize = 1000;
double[,] temperatureData = new double[gridSize, gridSize];
Parallel.For(0, gridSize, i =>
{
for(int j = 0; j < gridSize; j++)
{
temperatureData[i, j] = SimulateTemperatureChange(i, j);
}
});
Console.WriteLine("Temperature simulation completed.");
}
static double SimulateTemperatureChange(int x, int y)
{
// Complex calculation to simulate temperature change
return Math.Sin(x) * Math.Cos(y);
}
}
}
在这个例子中,Parallel.For它用于同时对网格数据点运行温度模拟,从而大大减少完成计算所需的时间。
使用并行编程构建可扩展的 Web 应用程序
Web应用程序必须高效地处理大量并发用户。使用并行编程进行数据处理和后台任务可以提高响应速度和可扩展性。
实际案例:处理并发 Web 请求
考虑一个用户可以搜索商品的电子商务应用程序。通过并行处理搜索查询,该应用程序可以在不降低性能的情况下处理更多用户。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EcommerceApp
{
class Program
{
static void Main(string[] args)
{
List<string> searchTerms = new List<string> { "laptop", "smartphone", "camera" };
List<Task<List<string>>> searchTasks = searchTerms.Select(term =>
Task.Run(() => SearchProducts(term))
).ToList();
Task.WhenAll(searchTasks);
foreach (var task in searchTasks)
{
var result = task.Result;
Console.WriteLine($"Search completed for {result.Count} products.");
}
}
static List<string> SearchProducts(string term)
{
// Simulate product search operation
Task.Delay(1000).Wait();
return new List<string> { $"{term} 1", $"{term} 2", $"{term} 3" };
}
}
}
此代码演示了如何同时运行多个产品搜索操作,通过减少返回搜索结果的时间来改善用户体验。
游戏开发及其他行业应用案例
游戏开发通常包含物理计算、人工智能行为和渲染等复杂任务,这些任务可以从并行编程中受益。
实际案例:实时物理模拟
并行编程可以帮助同时处理多个游戏对象的物理计算,从而确保流畅的游戏体验。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GameDevelopment
{
class Program
{
static void Main(string[] args)
{
List<GameObject> gameObjects = InitializeGameObjects(1000);
Parallel.ForEach(gameObjects, gameObject =>
{
gameObject.UpdatePhysics();
});
Console.WriteLine("Physics update completed.");
}
static List<GameObject> InitializeGameObjects(int count)
{
List<GameObject> gameObjects = new List<GameObject>();
for (int i = 0; i < count; i++)
{
gameObjects.Add(new GameObject());
}
return gameObjects;
}
}
class GameObject
{
public void UpdatePhysics()
{
// Simulate complex physics calculations
Task.Delay(10).Wait();
}
}
}
在这种情况下,Parallel.ForEach它用于同时更新数千个游戏对象的物理特性,从而提高游戏的性能和响应速度。
金融服务中的并行编程
金融服务业也从并行编程中获益匪浅。风险计算、欺诈检测和市场模拟等任务都非常适合并行处理。
真实案例:风险计算
银行和金融机构经常需要计算众多投资组合的风险。并行编程可以显著加快这些计算速度。
using System;
using System.Threading.Tasks;
namespace FinancialServices
{
class Program
{
static void Main(string[] args)
{
int numberOfPortfolios = 1000;
double[] riskValues = new double[numberOfPortfolios];
Parallel.For(0, numberOfPortfolios, i =>
{
riskValues[i] = CalculateRisk(i);
});
Console.WriteLine("Risk calculation completed.");
}
static double CalculateRisk(int portfolioId)
{
// Simulate complex risk calculation
Task.Delay(100).Wait();
return new Random().NextDouble() * 100;
}
}
}
在这个例子中,Parallel.For可以同时计算多个投资组合的风险,从而显著减少总处理时间。
生物信息学中的并行编程
生物信息学涉及生物数据的分析,通常需要大量的计算。并行编程可以加速测序和基因分析等任务。
现实生活中的例子:DNA测序
并行编程可用于同时处理不同的DNA数据片段。
using System;
using System.Threading.Tasks;
namespace Bioinformatics
{
class Program
{
static void Main(string[] args)
{
string[] dnaSegments = new string[] { "AGTC", "GATTACA", "CGTA" };
Parallel.ForEach(dnaSegments, segment =>
{
ProcessSegment(segment);
});
Console.WriteLine("DNA sequencing completed.");
}
static void ProcessSegment(string segment)
{
// Simulate DNA segment processing
Task.Delay(100).Wait();
Console.WriteLine($"Processed segment: {segment}");
}
}
}
通过并行处理每个DNA片段,测序和分析的总时间大大减少。
并行编程最佳实践
遵循最佳实践可确保您的并行代码具有可维护性、高效性和健壮性。
编写可维护的并行代码
保持代码清晰且文档齐全。将代码模块化,以隔离并行部分。
确保螺纹安全
使用锁、互斥锁和信号量等同步机制来确保线程安全。
private static readonly object lockObj = new object();
void SafeMethod()
{
lock (lockObj)
{
// Thread-safe code
}
}
测试并行应用程序
对并行代码进行全面测试,检查是否存在边界情况、竞争条件以及是否正确处理共享资源。
结论
这本全面的指南带你从并行编程的基础知识入手,逐步深入到 C# 和 .NET 中的高级技巧和实际应用。你学习了如何搭建开发环境,探索了任务并行库 (TPL),并发现了该类Parallel在数据并行和任务并行方面的强大功能。此外,你还深入研究了……