在 C 语言中并行运行特定任务的正确方法#

Proper way to run a specific task in parallel in C#

提问人:Tenza 提问时间:3/8/2023 最后编辑:Theodor ZouliasTenza 更新时间:3/8/2023 访问量:261

问:

我有一个运行并执行某些方法的 Windows C# BackgroundService 进程。我想利用系统上的多线程运行此方法以尽可能多地进行处理。该方法包含一个数据库事务,并在其中根据该事务执行一些文件 I/O。我不需要任务的结果,只需要尽快运行它们。DoWork

目前,我让它运行如下例所示。这有效,我确实看到了性能的提高,但这似乎不是正确的方法。我也看到了使用的能力,但我看到了与 .我想做的是不断利用可用的线程来执行。我这样做是否正确,或者有更好的方法可以做到这一点?Task.RunWaitAllParallel.ForDoWork

编辑:现在,除了对 .DoWork

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation(
        "Consume Scoped Service Hosted Service running.");

    Parallel.For(0, 8, task => {
        await DoWork(stoppingToken);
    });

    await Task.Delay(Timeout.Infinite, stoppingToken);
}

private async Task DoWork(CancellationToken stoppingToken)
{
    _logger.LogInformation(
        "Consume Scoped Service Hosted Service is working.");

    using (var transactionScope = new TransactionScope())      
    {      
       try      
       {      
          using (SqlConnection connection = new SqlConnection(connectionString))      
          {      
                connection.Open();      
                // get initial query which gets the row with data needed
                // to perform the file I/O and build request
                var query = "SELECT TOP(1) FROM MyTable" +
                    " WITH (readpast, rowlock, updlock)";
                var row = connection.Query<MyType>(query);

                // based on query, perform file i/o
                var file = File.ReadAllText(row.FileName);
                var data = JsonSerializer.Deserialize<MyData>(file);

                var item = new MyObject() { Id = data.Id, Name = data.Name };
                
                // call a WCF web service using item object, the web service
                // has no asynchronous methods
                service.SendRequest(item);

                // update the transaction and then close
                var updateQuery = "UPDATE MyTable SET Status = @Status WHERE Id = @Id";
                connection.Execute<MyType>(updateQuery);
                transactionScope.Complete();                 
          }           
       }      
       catch(Exception ex)      
       {      
          // Log error      
          transactionScope.Dispose();      
       }      
    }    

}
C# 多线程异 处理 任务并行库

评论

2赞 Alexei Levenkov 3/8/2023
stackoverflow.com/questions/11564506/......(唯一可以建议的是,当您决定不显示真正重要的代码(异步数据库和文件 I/O)时,如果您决定将该信息包含在帖子中,请确保重新阅读最小可重现示例指南)。
2赞 Guru Stron 3/8/2023
Parallel.For不是任务感知,JIC。
0赞 Tenza 3/8/2023
@AlexeiLevenkov我已经更新了问题,提供了更多信息,提供了有关其工作原理的一些上下文,但它目前同步运行所有数据库和文件 I/O
0赞 Stephen Cleary 3/8/2023
如果你有一个包含要完成的工作的数据库表,你需要协调工作,这样不同的线程就不会都试图抓取同一个项目。也没有循环,因此每个线程将处理一个项目,然后退出。
0赞 Tenza 3/8/2023
@StephenCleary 它已经没有抓取相同的项目,请参阅readpast、rowlock和updlock提示。该功能按我想要的方式工作,我想确认是否有更好的方法来并行运行多个任务。基本上,我希望运行多个线程或任务来执行工作,以便我可以同时处理更多文件。就循环而言,一个线程是否应该处理多个项目?

答:

1赞 Theodor Zoulias 3/8/2023 #1

由于面向 .NET 7,因此可以使用 .NET 6 API Parallel.ForEachAsync

ParallelOptions options = new()
{
    MaxDegreeOfParallelism = 2,
    CancellationToken = stoppingToken,
};

await Parallel.ForEachAsync(Enumerable.Range(0, 8), options, async (_, ct) =>
{
    await DoWork(ct);
});

您可以尝试使用 MaxDegreeOfParallelism 选项,直到找到手头任务的最佳值。从一个小值开始,逐渐增加它,直到性能停止提高。

评论

0赞 Tenza 3/9/2023
在这种情况下,除了使用并行处理之外,还有什么方法可以提高性能吗?由于这一切都是同步的,而且我确实有一个网络请求,因此并行处理似乎只有这么多好处。Parallel.For
0赞 Theodor Zoulias 3/9/2023
@Tenza,有可能优化与数据库相关的工作。通过减少查询(批处理、批量操作)执行更多工作,或者改进查询执行计划(添加索引等)。但这超出了这个问题的范围。