限制在ASP.NET Core 6中运行线程为一个,使用TaskScheduler。

huangapple go评论57阅读模式
英文:

Limit Running Threads to one in ASP.NET Core 6 using TaskScheduler

问题

我们有一个ASP.NET Core 6应用程序。用户上传文件,然后我们对这些文件进行处理。

处理文件是一个消耗CPU和内存的操作。我们计划将这些操作移到我们的主应用程序之外,但在这之前,我们决定尝试在上传文件后在后台运行任务,并在操作完成后发送电子邮件。

因此,文件的处理过程如下:

public async Task ProcessFile(string filePath, ...) 
{
    var stage1 = await ProcessStage1(...);
    var stage2 = await ProcessStage2(...);
    await SendEmail(..);
}

从上传文件开始,我们运行任务:

public async Task UploadFile(Stream stream....)
{
   // .... 上传文件

   Task.Factory.StartNew(() => {
     ProcessFile(....);
   })
}

由于这个过程很重,所以它会消耗大量内存,因此我们决定通过限制线程数为两个或一个来将处理限制为一次处理一个文件。

阅读 .NET TaskScheduler 限制线程数为一的示例 中的示例后,我实现了示例中提到的代码,并将自定义的 TaskScheduler 添加为单例,如下所示:

public interface IProcessFileFactory {
    public TaskScheduler Scheduler { get; }
    public TaskFactory Factory { get; }
} 

public ProcessFileFactory: IProcessFileFactory 
{
    public TaskScheduler Scheduler { get; }
    public TaskFactory Factory { get; }

    public ProcessFileFactory()
    {
      Scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
      Factory = new TaskFactory(Scheduler);
    }
   
    // 然后将其添加为DI中的单例
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(TaskScheduler, LimitedConcurrencyLevelTaskScheduler)
    }
   
    // 然后使用它

    IProcessFileFactory.Factory.StartNew(() => {
        ProcessFile(...),
        ..., IProcessFileFactory.Scheduler);
}

然后我尝试了不同的方式:

Services.AddSingleton<TaskScheduler>(x => new LimitedConcurrencyLevelTaskScheduler(2));

// 然后使用它
Task.Factory.StartNew(() => {
    ProcessFile() 
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler);

这两种情况都没有奏效,它仍然处理超过2个文件。

英文:

We have an ASP.NET Core 6 application. The user upload files and we do process those files.

Processing the files is a CPU and memory extensive operation. We are planning to take these operations outside of our main application, but until we do that we decided to try to run the task in background after uploading the file, and send email when the operation is done.

So the process of file is:

public async Task ProcessFile(string filePath, ...) 
{
    var stage1 = await ProcessStage1(...);
    var stage2 = await ProcessStage2(...);
    await SendEmail(..);
}

From the upload the file we run the task.run

public async Task UploadFile(Stream stream....)
{
   // .... uploading the file 

   Task.Factory.StartNew(() =&gt; {
     ProcessFile(....);
   })
}

Because the process is heavy, so it is consuming lots of memory, so we decided to limit the process to one file at one time by limiting the threads to only two or one.

Reading the example in .NET TaskScheduler to limit the threads to one , I implemented the code mentioned in the example, and I added the custom TaskScheduler as singleton as the following:

public interface IProcessFileFactory {
 public TaskScheduler Scheduler { get; }
    public TaskFactory Factory { get; }
} 

public ProcessFileFactory: IProcessFileFactory 
{
    public TaskScheduler Scheduler { get; }
    public TaskFactory Factory { get; }

    public ProcessFileFactory()
    {
      Scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
      Factory = new TaskFactory(Scheduler);
    }
   
    // then add it as singlton in DI
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(TaskScheduler, LimitedConcurrencyLevelTaskScheduler)
    }

    /// and then use it 

    IProcessFileFactory.Factory.StartNew(() =&gt; {
    ProcessFile(...),
    ..., IProcessFileFactory.Scheduler);

And I tried it in a different way

 Services.AddSignlton&lt;TaskScheduler&gt;(x =&gt; new LimitedConcurrencyLevelTaskScheduler (2) );

// and then use it
   Task.Factory.StartNew(() {
     ProcessFile() 
    },
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler);

Both cases didn't work, and it is processing files for more than 2.

答案1

得分: 1

That is the correct solution, as I describe on my blog.

TaskScheduler is a low-level primitive that isn't usually used with async code. It sounds like you just need a queue service. As long as you process the work items one at a time, nothing more complex is necessary.

As a reminder, in-memory queues such as channels are not durable and thus you can lose work on any application shutdown. A proper solution uses a durable queue, and (preferably) a separate worker process outside your main application to perform the operations.

英文:

> We are planning to take these operations outside of our main application...

That is the correct solution, as I describe on my blog.

> ... TaskScheduler ...

TaskScheduler is a low-level primitive that isn't usually used with async code. It sounds like you just need a queue service. As long as you process the work items one at a time, nothing more complex is necessary.

As a reminder, in-memory queues such as channels are not durable and thus you can lose work on any application shutdown. A proper solution uses a durable queue, and (preferably) a separate worker process outside your main application to perform the operations.

huangapple
  • 本文由 发表于 2023年6月5日 04:21:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76402257.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定