windows C#-异步编程场景(二)

news/2024/11/23 13:02:09/

等待多个任务完成

你可能发现自己处于需要并行检索多个数据部分的情况。 Task API 包含两种方法(即 Task.WhenAll 和 Task.WhenAny),这些方法允许你编写在多个后台作业中执行非阻止等待的异步代码。

此示例演示如何为一组 User 捕捉 userId 数据。

private static async Task<User> GetUserAsync(int userId)
{// Code omitted://// Given a user Id {userId}, retrieves a User object corresponding// to the entry in the database with {userId} as its Id.return await Task.FromResult(new User() { id = userId });
}private static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{var getUserTasks = new List<Task<User>>();foreach (int userId in userIds){getUserTasks.Add(GetUserAsync(userId));}return await Task.WhenAll(getUserTasks);
}

以下是另一种更简洁的使用 LINQ 进行编写的方法:

private static async Task<User[]> GetUsersAsyncByLINQ(IEnumerable<int> userIds)
{var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();return await Task.WhenAll(getUserTasks);
}

尽管它的代码较少,但在混合 LINQ 和异步代码时需要谨慎使用。 因为 LINQ 使用延迟的执行,因此异步调用将不会像在 foreach 循环中那样立刻发生,除非强制所生成的序列通过对 .ToList() 或 .ToArray() 的调用循环访问。 上述示例使用 Enumerable.ToArray 预先执行查询,并将结果存储在数组中。 这会强制代码 id => GetUserAsync(id) 运行并启动任务。

重要信息和建议

对于异步编程,有一些细节需要注意,以防止意外行为。

1. async 方法需要在主体中有 await 关键字,否则它们将永不暂停!

这一点需牢记在心。 如果 await 未用在 async 方法的主体中,C# 编译器将生成一个警告,但此代码将会以类似普通方法的方式进行编译和运行。 这种方式非常低效,因为由 C# 编译器为异步方法生成的状态机将不会完成任何任务。

2. 添加“Async”作为编写的每个异步方法名称的后缀。

这是 .NET 中的惯例,以便更为轻松地区分同步和异步方法。 未由代码显式调用的某些方法(如事件处理程序或 Web 控制器方法)并不一定适用。 由于它们未由代码显式调用,因此对其显式命名并不重要。

3. async void 应仅用于事件处理程序。

async void 是允许异步事件处理程序工作的唯一方法,因为事件不具有返回类型(因此无法利用 Task 和 Task<T>)。 其他任何对 async void 的使用都不遵循 TAP 模型,且可能存在一定使用难度,例如:

async void 方法中引发的异常无法在该方法外部被捕获。
async void 方法很难测试。
async void 方法可能会导致不良副作用(如果调用方不希望方法是异步的话)。
在 LINQ 表达式中使用异步 lambda 时请谨慎

LINQ 中的 Lambda 表达式使用延迟执行,这意味着代码可能在你并不希望结束的时候停止执行。 如果编写不正确,将阻塞任务引入其中时可能很容易导致死锁。 此外,此类异步代码嵌套可能会对推断代码的执行带来更多困难。 Async 和 LINQ 的功能都十分强大,但在结合使用两者时应尽可能小心。

4. 采用非阻止方式编写等待任务的代码

通过阻止当前线程来等待 Task 完成的方法可能导致死锁和已阻止的上下文线程,且可能需要更复杂的错误处理方法。 

0b8c7ab6208a4c3b966cee89efa35dd0.png

5. 如果可能,请考虑使用 ValueTask

从异步方法返回 Task 对象可能在某些路径中导致性能瓶颈。 Task 是引用类型,因此使用它意味着分配对象。 如果使用 async 修饰符声明的方法返回缓存结果或以同步方式完成,那么额外的分配在代码的性能关键部分可能要耗费相当长的时间。 如果这些分配发生在紧凑循环中,则成本会变高。 有关详细信息,请参阅通用的异步返回类型。

6. 考虑使用 ConfigureAwait(false)

常见的问题是“应何时使用 Task.ConfigureAwait(Boolean) 方法?”。 该方法允许 Task 实例配置其 awaiter。 这是一个重要的注意事项,如果设置不正确,可能会影响性能,甚至造成死锁。 

7. 编写状态欠缺的代码

请勿依赖全局对象的状态或某些方法的执行。 请仅依赖方法的返回值。 为什么?

这样更容易推断代码。
这样更容易测试代码。
混合异步和同步代码更简单。
通常可完全避免争用条件。
通过依赖返回值,协调异步代码可变得简单。
(好处)它非常适用于依赖关系注入。
建议的目标是实现代码中完整或接近完整的引用透明度。 这么做能获得可预测、可测试和可维护的代码库。

完整示例

下列代码是示例的 Program.cs 文件的完整文本。

using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.AspNetCore.Mvc;class Button
{public Func<object, object, Task>? Clicked{get;internal set;}
}class DamageResult
{public int Damage{get { return 0; }}
}class User
{public bool isEnabled{get;set;}public int id{get;set;}
}public class Program
{private static readonly Button s_downloadButton = new();private static readonly Button s_calculateButton = new();private static readonly HttpClient s_httpClient = new();private static readonly IEnumerable<string> s_urlList = new string[]{"https://learn.microsoft.com","https://learn.microsoft.com/aspnet/core","https://learn.microsoft.com/azure","https://learn.microsoft.com/azure/devops","https://learn.microsoft.com/dotnet","https://learn.microsoft.com/dotnet/desktop/wpf/get-started/create-app-visual-studio","https://learn.microsoft.com/education","https://learn.microsoft.com/shows/net-core-101/what-is-net","https://learn.microsoft.com/enterprise-mobility-security","https://learn.microsoft.com/gaming","https://learn.microsoft.com/graph","https://learn.microsoft.com/microsoft-365","https://learn.microsoft.com/office","https://learn.microsoft.com/powershell","https://learn.microsoft.com/sql","https://learn.microsoft.com/surface","https://dotnetfoundation.org","https://learn.microsoft.com/visualstudio","https://learn.microsoft.com/windows","https://learn.microsoft.com/maui"};private static void Calculate(){// <PerformGameCalculation>static DamageResult CalculateDamageDone(){return new DamageResult(){// Code omitted://// Does an expensive calculation and returns// the result of that calculation.};}s_calculateButton.Clicked += async (o, e) =>{// This line will yield control to the UI while CalculateDamageDone()// performs its work. The UI thread is free to perform other work.var damageResult = await Task.Run(() => CalculateDamageDone());DisplayDamage(damageResult);};// </PerformGameCalculation>}private static void DisplayDamage(DamageResult damage){Console.WriteLine(damage.Damage);}private static void Download(string URL){// <UnblockingDownload>s_downloadButton.Clicked += async (o, e) =>{// This line will yield control to the UI as the request// from the web service is happening.//// The UI thread is now free to perform other work.var stringData = await s_httpClient.GetStringAsync(URL);DoSomethingWithData(stringData);};// </UnblockingDownload>}private static void DoSomethingWithData(object stringData){Console.WriteLine("Displaying data: ", stringData);}// <GetUsersForDataset>private static async Task<User> GetUserAsync(int userId){// Code omitted://// Given a user Id {userId}, retrieves a User object corresponding// to the entry in the database with {userId} as its Id.return await Task.FromResult(new User() { id = userId });}private static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds){var getUserTasks = new List<Task<User>>();foreach (int userId in userIds){getUserTasks.Add(GetUserAsync(userId));}return await Task.WhenAll(getUserTasks);}// </GetUsersForDataset>// <GetUsersForDatasetByLINQ>private static async Task<User[]> GetUsersAsyncByLINQ(IEnumerable<int> userIds){var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();return await Task.WhenAll(getUserTasks);}// </GetUsersForDatasetByLINQ>// <ExtractDataFromNetwork>[HttpGet, Route("DotNetCount")]static public async Task<int> GetDotNetCount(string URL){// Suspends GetDotNetCount() to allow the caller (the web server)// to accept another request, rather than blocking on this one.var html = await s_httpClient.GetStringAsync(URL);return Regex.Matches(html, @"\.NET").Count;}// </ExtractDataFromNetwork>static async Task Main(){Console.WriteLine("Application started.");Console.WriteLine("Counting '.NET' phrase in websites...");int total = 0;foreach (string url in s_urlList){var result = await GetDotNetCount(url);Console.WriteLine($"{url}: {result}");total += result;}Console.WriteLine("Total: " + total);Console.WriteLine("Retrieving User objects with list of IDs...");IEnumerable<int> ids = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };var users = await GetUsersAsync(ids);foreach (User? user in users){Console.WriteLine($"{user.id}: isEnabled={user.isEnabled}");}Console.WriteLine("Application ending.");}
}// Example output:
//
// Application started.
// Counting '.NET' phrase in websites...
// https://learn.microsoft.com: 0
// https://learn.microsoft.com/aspnet/core: 57
// https://learn.microsoft.com/azure: 1
// https://learn.microsoft.com/azure/devops: 2
// https://learn.microsoft.com/dotnet: 83
// https://learn.microsoft.com/dotnet/desktop/wpf/get-started/create-app-visual-studio: 31
// https://learn.microsoft.com/education: 0
// https://learn.microsoft.com/shows/net-core-101/what-is-net: 42
// https://learn.microsoft.com/enterprise-mobility-security: 0
// https://learn.microsoft.com/gaming: 0
// https://learn.microsoft.com/graph: 0
// https://learn.microsoft.com/microsoft-365: 0
// https://learn.microsoft.com/office: 0
// https://learn.microsoft.com/powershell: 0
// https://learn.microsoft.com/sql: 0
// https://learn.microsoft.com/surface: 0
// https://dotnetfoundation.org: 16
// https://learn.microsoft.com/visualstudio: 0
// https://learn.microsoft.com/windows: 0
// https://learn.microsoft.com/maui: 6
// Total: 238
// Retrieving User objects with list of IDs...
// 1: isEnabled= False
// 2: isEnabled= False
// 3: isEnabled= False
// 4: isEnabled= False
// 5: isEnabled= False
// 6: isEnabled= False
// 7: isEnabled= False
// 8: isEnabled= False
// 9: isEnabled= False
// 0: isEnabled= False
// Application ending.

 


http://www.ppmy.cn/news/1549287.html

相关文章

【机器学习】——朴素贝叶斯模型

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

信创时代的数据库之路:2024 Top10 国产数据库迁移与同步指南

数据库一直是企业数字化和创新的重要基础设施之一。从传统的关系型数据库到非关系型数据库、分析型数据库&#xff0c;再到云数据库和多模数据库&#xff0c;这一领域仍在持续变革中&#xff0c;各种新型数据库产品涌现&#xff0c;数据管理的能力和应用场景也由此得到了扩展。…

在 C# 中使用 WebClient 实现文件上传,并在 IIS 上部署应用程序

在 C# 中使用 WebClient 实现文件上传&#xff0c;并在 IIS 上部署应用程序 步骤 1: 创建文件上传的 ASP.NET 应用程序步骤 2: 使用 WebClient 上传文件步骤 3: 在 IIS 上部署应用程序总结 在 C# 中使用 WebClient 实现文件上传&#xff0c;并在 IIS 上部署应用程序&#xff0c…

SpringSecurity创建一个简单的自定义表单的认证应用

1、SpringSecurity 自定义表单 在 Spring Security 中创建自定义表单认证应用是一个常见的需求&#xff0c;特别是在需要自定义登录页面、认证逻辑或添加额外的表单字段时。以下是一个详细的步骤指南&#xff0c;帮助你创建一个自定义表单认证应用。 2、基于 SpringSecurity 的…

推荐一个基于协程的C++(lua)游戏服务器

1.跨平台 支持win,mac,linux等多个操作系统 2.协程系统 使用汇编实现的上下文模块&#xff0c;C模块实现的协程调度器&#xff0c;使用共享栈&#xff0c;支持开启上千万协程&#xff0c;一个协程大概使用2000字节 3.rpc系统 强大的rpc系统&#xff0c;功能模块可以使用c或…

TDK推出第二代用于汽车安全应用的6轴IMU

近日&#xff0c;据外媒报道&#xff0c;TDK株式会社推出用于汽车安全应用的第二代6轴 IMU&#xff0c;即为TDK InvenSense SmartAutomotive MEMS传感器系列增加了IAM-20685HP和IAM-20689&#xff0c;为决策算法提供可靠的运动数据&#xff0c;并实时准确地检测车辆动态。这对于…

WordPress添加类似说说、微博的时间轴微语页面

这个版本的WordPress可以直接使用&#xff0c;CSS样式可以完美兼容。效果如图 使用方法&#xff1a; 一、后台配置 新建微语功能 将下面的代码复制粘贴到主题的functions.php函数文件中&#xff0c;为WordPress添加微语功能。添加完成后&#xff0c;可以在WordPress后台菜单…

秋意浓,森林披金装

秋意浓&#xff0c;森林披金装&#xff0c; 枫叶如火&#xff0c;漫山遍野狂。 松间轻风送寒意&#xff0c; 鸟鸣悠扬入云翔。 林间小径蜿蜒行&#xff0c; 落叶铺成金色毯。 溪水潺潺绕石转&#xff0c; 映出天边一抹霞。 野菊点缀在草间&#xff0c; 白云悠悠随意闲。…