【问题标题】:The task is taking unreasonable time to finish任务花费了不合理的时间来完成
【发布时间】:2022-01-15 12:40:16
【问题描述】:

我正在使用库 CsvHelper 来读取与解决方案位于同一位置的文本文件中的一些数据。文本文件有大约 12 000 行,这不算多。 但这需要超过 10 分钟左右的时间,并让页面/浏览器说“此页面没有响应”。

在不使用 Tasks 而是直接在 Main 方法中读取相同文件时,只需不到一秒的时间即可获得相同数量 (12 000) 的记录。 我是否以错误的方式使用任务?

//method within the caller class
private static async Task<IResult> GetEmployees(IEmployeeData data)
    {
        try
        {
            return Results.Ok(await data.GetEmployees());
        }
        catch (Exception ex)
        {
            return Results.Problem(ex.Message);
        }
    }
    
//method within class that implements ICsvDataAccess
public  Task<IEnumerable<T>> LoadData<T>(string path, bool hasHeaderRecord = false, string delimiter = ";")
    {
        return Task.Run(() => 
        {
            var config = new CsvConfiguration(CultureInfo.InvariantCulture)
            {
                HasHeaderRecord = hasHeaderRecord, Delimiter = delimiter
            };

            using (var reader = new StreamReader(path))
            using (var csv = new CsvReader(reader, config))
            {
                return csv.GetRecords<T>().ToList().AsEnumerable();
            }
        });
    }   

//within EmployeeData class which implements IEmployeeData interface
private readonly ICsvDataAccess _file;
public Task<IEnumerable<EmployeeModel>> GetEmployees() =>
        _file.LoadData<EmployeeModel>(path: "data.txt");
        
        

public interface IEmployeeData
{
    Task<IEnumerable<EmployeeModel>> GetEmployees();
}       
        
    
public interface ICsvDataAccess
{
    Task<IEnumerable<T>> LoadData<T>(string path, bool hasHeaderRecord = false, string delimiter = ";");
}

谢谢!

【问题讨论】:

  • 您是否已按照说明进行操作?究竟哪条线需要这么长时间?
  • 您能否重命名两个GetEmployees 方法之一?目前你的代码 sn-p 很混乱,因为这个名字冲突。
  • 附带说明,异步方法by conventionAsync 后缀命名。 LoadDataAsyncGetEmployeesAsync 是正确的。 LoadData 方法也违反了 not exposing asynchronous wrappers for synchronous methods 的准则。
  • 您是否在所有这些 Task 返回方法中遗漏了很多 async/await
  • 这真的完成了吗?听起来您已经通过将任务排队到不适合他们的 ASP.NET 线程上来击中deadlock。如果您要异步加载 CSV,则不应使用请求线程执行此操作,因为您可能希望向用户返回响应,然后该用户具有加载微调器或等待 CSV 准备就绪的东西,所以我建议检查您正在使用的 .NET Web 框架如何让您使用异步代码。

标签: c# .net task task-parallel-library csvhelper


【解决方案1】:

Task.Run 内运行GetRecords 不会使这段代码异步,它只使用一个第二个 线程来执行第一个线程也可以执行的阻塞代码。如果您在 ASP.NET Core 中运行此代码,其中每个请求都由一个单独的线程提供服务,那么该代码只会浪费一个线程。

要真正异步读取记录,请使用GetRecordsAsync() 并返回IAsyncEnumerable

public async IAsyncEnumerable<T> LoadDataAsync<T>(string path, bool hasHeaderRecord = false, string delimiter = ";")
{

        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            HasHeaderRecord = hasHeaderRecord, Delimiter = delimiter
        };

        using (var reader = new StreamReader(path))
        using (var csv = new CsvReader(reader, config))
        {
            await foreach(var rec in csv.GetRecords<T>())
            {
                yield return rec;
            }
        }
} 

await foreach/yield return 循环确保在迭代 IAsyncEnumerable 的客户端代码完成之前不会释放读取器。

ASP.NET Core 5 及更高版本支持 IAsyncEnumerable,这意味着您可以编写:

[HttpGet]
public IAsyncEnumerable<ThatData> Get()
{
    return LoadDataAsync<ThatData>(...);
} 

[HttpGet]
public IActionResult Get()
{
    return Ok(LoadDataAsync<ThatData>(...);
}

ASP.NET Core 5 会在将结果序列化为 JSON 之前缓冲结果,但 ASP.NET Core 6 会start serializing the records without buffering

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-04
    • 1970-01-01
    • 2020-12-29
    • 2020-07-17
    • 1970-01-01
    • 2020-06-30
    • 2015-05-27
    • 2013-06-12
    相关资源
    最近更新 更多