MST

星途 面试题库

面试题:C# 异步编程中的性能优化与资源管理

在一个高并发的 C# 应用程序中,大量使用 Async 和 Await 进行异步 I/O 操作。请阐述如何对这些异步操作进行性能优化,避免资源耗尽(如线程池资源)。同时,描述如何使用 `SemaphoreSlim` 或其他合适的机制来限制并发数,确保系统稳定运行,并结合代码示例说明。
46.9万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试

异步操作性能优化及避免资源耗尽

  1. 合理设置线程池参数
    • 可以通过 ThreadPool.SetMinThreadsThreadPool.SetMaxThreads 方法设置线程池的最小和最大线程数。例如,如果知道应用程序主要是 I/O 密集型,可以适当降低最小线程数,因为 I/O 操作通常不会占用线程执行,这样可以减少线程上下文切换开销。
    // 设置最小工作线程数为10,最小I/O完成线程数为10
    ThreadPool.SetMinThreads(10, 10); 
    // 设置最大工作线程数为100,最大I/O完成线程数为100
    ThreadPool.SetMaxThreads(100, 100); 
    
  2. 避免不必要的异步/同步切换
    • 确保异步方法内部尽量保持异步操作,避免在异步方法中调用同步方法,因为这可能导致线程池线程阻塞,浪费资源。例如,如果有一个异步方法 AsyncMethod,内部调用了一个同步的数据库查询方法 SyncDbQuery,应将 SyncDbQuery 改为异步版本 AsyncDbQuery
    // 错误示例
    public async Task<int> AsyncMethod()
    {
        return SyncDbQuery();
    }
    // 正确示例
    public async Task<int> AsyncMethod()
    {
        return await AsyncDbQuery();
    }
    
  3. 使用异步流(IAsyncEnumerable
    • 对于处理大量数据的异步操作,使用 IAsyncEnumerable 可以逐块处理数据,而不是一次性加载所有数据到内存。这在处理大型文件或数据库结果集时很有用。
    public async IAsyncEnumerable<int> GetLargeDataAsync()
    {
        // 模拟从数据库逐行读取数据
        for (int i = 0; i < 10000; i++)
        {
            await Task.Delay(10);
            yield return i;
        }
    }
    
    • 消费异步流时:
    public async Task ProcessDataAsync()
    {
        await foreach (var item in GetLargeDataAsync())
        {
            // 处理item
            Console.WriteLine(item);
        }
    }
    

使用 SemaphoreSlim 限制并发数

SemaphoreSlim 可以用来限制并发操作的数量。例如,假设一个应用程序需要从多个 API 获取数据,但每个 API 有并发限制,我们可以使用 SemaphoreSlim 来控制并发请求数。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(5, 5); // 初始化并发数为5

    static async Task Main()
    {
        var tasks = new List<Task>();
        for (int i = 0; i < 20; i++)
        {
            tasks.Add(FetchDataAsync(i));
        }
        await Task.WhenAll(tasks);
        _semaphore.Dispose();
    }

    static async Task FetchDataAsync(int id)
    {
        await _semaphore.WaitAsync();
        try
        {
            // 模拟API调用
            Console.WriteLine($"Task {id} started");
            await Task.Delay(1000);
            Console.WriteLine($"Task {id} completed");
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

在上述代码中,SemaphoreSlim 初始化允许的并发数为 5。每个 FetchDataAsync 方法在执行实际操作前先调用 WaitAsync 获取信号量,如果信号量已达上限则等待。操作完成后调用 Release 释放信号量,这样其他任务就可以获取信号量继续执行,从而保证系统稳定运行,避免因过多并发导致资源耗尽。