避免在C#循环中使用await的方法小结

 更新时间:2024年09月10日 11:40:51   作者:一个程序员_zhangzhen  
在C#中,异步编程因其能够提升应用程序性能和响应能力而变得越来越流行,async和await关键字使得编写异步代码变得更加容易,但如果使用不当,它们也可能引入一些陷阱,所以本文我们将探讨为什么应该避免在C#循环中使用await,并讨论一些更高效地处理异步操作的替代方法

引言

在C#中,异步编程因其能够提升应用程序性能和响应能力而变得越来越流行。async和await关键字使得编写异步代码变得更加容易,但如果使用不当,它们也可能引入一些陷阱。一个常见的错误是在循环中使用await,这可能导致性能瓶颈和意外行为。在本文中,我们将探讨为什么应该避免在C#循环中使用await,并讨论一些更高效地处理异步操作的替代方法。

一 在循环中使用await的问题

1、顺序执行

当在循环中使用await时,每次迭代都会等待前一次迭代完成后再开始。这导致了顺序执行,抵消了异步编程的好处。请看以下示例:

foreach (var item in items) 
{ 
    await ProcessItemAsync(item); 
}

在这段代码中,每次迭代都会等待ProcessItemAsync完成后再进行下一次迭代。如果ProcessItemAsync需要较长时间才能完成,这会导致性能不佳。

示例场景

假设我们需要通过异步下载处理一组URL的内容。在循环中使用await的代码如下:

foreach (var url in urls) 
{ 
    var content = await DownloadContentAsync(url); 
    ProcessContent(content); 
}

在这种情况下,每个URL都是一个接一个地处理,导致总执行时间是所有单个下载时间的总和。如果我们有10个URL,每个下载需要1秒,总执行时间将大约是10秒。

2、资源争用

在循环中使用await还可能导致资源争用。每次迭代都会占用资源(如内存和网络连接)直到等待的任务完成。这可能导致可用资源的耗尽,尤其是在处理大量任务时。

二 更好的替代方法

1、使用Task.WhenAll

为了并发执行异步操作,我们可以使用Task.WhenAll。这种方法允许我们一次启动所有异步任务,并等待它们全部完成。以下是如何重写前面的示例:

using System.Diagnostics;
using System.Net;
 
namespace ConsoleApp1
{
    public class Program
    {
        public async static Task Main(string[] args)
        {
            var urls = new List<string>
            {
                "https://www.163.com",
                "https://www.microsoft.com",
                "https://www.baidu.com"
            };
            Stopwatch sw = Stopwatch.StartNew();
            foreach (var url in urls)
            {
                var content = await DownloadContentAsync(url);
                ProcessContent(content);
            }
            Console.WriteLine($"foreach总共用时:{sw.Elapsed.TotalSeconds}秒");
            Stopwatch sw2 = Stopwatch.StartNew();
            var downloadTasks = urls.Select(url => DownloadContentAsync(url)).ToArray();
            var contents = await Task.WhenAll(downloadTasks);
            foreach (var content in contents)
            {
                ProcessContent(content);
            }
            Console.WriteLine($"WhenAll总共用时:{sw2.Elapsed.TotalSeconds}秒");
        }
        public async static Task<string> DownloadContentAsync(string url)
        {
            using (var client = new HttpClient(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip }))
            {
                client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36");
                var response = await client.GetAsync(url);
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
        public static void ProcessContent(string content)
        {
            Console.WriteLine($"处理内容的长度: {content.Length}");// 这里可以添加更多的内容处理逻辑  }
        }
    }
}

在这个版本中,所有下载任务同时启动,我们等待它们全部完成后再处理结果。这种方法显著减少了总执行时间,因为任务是并行运行的。经测验,循环多少次,Task.WhenAll的速度比foreach的速度快大概多少倍!

2、使用Parallel.ForEachAsync

C#还提供了Parallel.ForEachAsync,它允许你在不阻塞主线程的情况下并行运行异步操作:

await Parallel.ForEachAsync(urls, async (url, cancellationToken) => 
{ 
    var content = await DownloadContentAsync(url); 
    ProcessContent(content); 
});

Parallel.ForEachAsync确保多个迭代可以并发运行,提升性能的同时保持代码的简洁和可读性。

Parallel.ForEachAsync的运行效率与Task.WhenAll的效率差不多

3、限制并发

在某些情况下,运行过多的并发任务可能会使系统资源不堪重负。我们可以通过使用SemaphoreSlim来限制并发级别:

这个在执行过程中产生了异常,等解决了在继续讨论

这种方法限制了并发任务的数量,更有效地管理资源使用。

三 结论

虽然await是C#异步编程的强大工具,但在循环中使用它可能导致性能不佳和资源争用。通过理解顺序执行的影响,并利用Task.WhenAll、Parallel.ForEachAsync和SemaphoreSlim等替代方法,我们可以编写更高效和健壮的异步代码。避免在循环中使用await并采用更好的模式将提升你的应用程序性能,使代码更易维护和扩展。遵循这些最佳实践,你可以充分利用C#异步编程的潜力。

以上就是避免在C#循环中使用await的方法小结的详细内容,更多关于避免C#循环使用await的资料请关注脚本之家其它相关文章!

相关文章

  • 利用C#实现访问远程硬盘的高效方案

    利用C#实现访问远程硬盘的高效方案

    随着数据存储需求的不断增加,越来越多的企业和开发者开始将文件存储从本地硬盘转移到远程存储解决方案中,在本篇文章中,我们将深入探讨如何使用C#访问远程硬盘,需要的朋友可以参考下
    2025-05-05
  • c# 命名空间和程序集

    c# 命名空间和程序集

    命名空间:用于对相关的类型进行逻辑分组,使用命名空间方便定位一个类型
    2012-10-10
  • C#省份城市下拉框联动简单实现方法

    C#省份城市下拉框联动简单实现方法

    这篇文章主要介绍了C#省份城市下拉框联动简单实现方法,涉及字典的定义与索引的用法,是非常实用的技巧,需要的朋友可以参考下
    2014-12-12
  • SQL语句删除和添加外键、主键的方法

    SQL语句删除和添加外键、主键的方法

    本文将详细介绍SQL语句删除和添加外键、主键的方法,需要的朋友可以参考下
    2012-11-11
  • C#日期时间类的使用方法(DateTime类、TimeSpan类与DateTimeOffset类)

    C#日期时间类的使用方法(DateTime类、TimeSpan类与DateTimeOffset类)

    在C#中我们常使用到关于时间的相关操作,这篇文章主要给大家介绍了关于C#日期时间类的使用方法,文中介绍的方法分别包括DateTime类、TimeSpan类与DateTimeOffset类的相关资料,需要的朋友可以参考下
    2023-11-11
  • C#实现回文检测的方法

    C#实现回文检测的方法

    这篇文章主要介绍了C#实现回文检测的方法,实例分析了C#使用栈进行回文检测的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • unity 鼠标悬停事件操作

    unity 鼠标悬停事件操作

    这篇文章主要介绍了unity 鼠标悬停事件操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • c#基于Redis实现轻量级消息组件的步骤

    c#基于Redis实现轻量级消息组件的步骤

    这篇文章主要介绍了c#基于Redis实现轻量级消息组件的步骤,帮助大家更好的理解和学习使用c#进行开发,感兴趣的朋友可以了解下
    2021-05-05
  • C#开发交互式命令行应用示例

    C#开发交互式命令行应用示例

    这篇文章主要为大家介绍了C#开发交互式命令行应用示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • C#调用barTender打印标签示例的实现

    C#调用barTender打印标签示例的实现

    Bartender是最优秀的条码打印软件,在企业里使用非常普遍,本文主要介绍了C#调用barTender打印标签示例的实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08

最新评论