“锁池”或其他用于处理 .NET Framework 后端内存密集型操作的解决方案?

问题描述 投票:0回答:1

我的后端应用程序采用表示图像数据的字节数组,并对它应用某些转换,例如更改分辨率等,然后将修改后的数据存储到磁盘。

在一些分析过程中,我发现如果有许多并发请求使用此转换实用程序,服务器应用程序的内存使用量将会激增,并且在较弱的硬件上可能会导致 OutOfMemoryException。

为了暂时解决这个问题,我将内存密集型操作包装在锁块中,有点像这样:

        public byte[] TransformImage(byte[] imageBytes)
        {
            lock (Lock)
            {
                // Do memory intensive work
            }
        }

这可行,但也有一个结果,理论上可以处理多个并发请求的机器现在只能一次处理一个请求,可能会影响用户体验。

我想我可以分配一个“锁池”或类似的东西,我可以根据可用内存进行扩展,但是缺乏描述这种解决方案的来源暗示我可能在这里使用了错误的方法。

有什么建议吗?

c# .net concurrency backend .net-4.8
1个回答
3
投票

如果您必须将一种方法的并发使用限制为多个,则要使用的正确类可能是 SemaphoreSlim:

代表 Semaphore 的轻量级替代方案,它限制可以同时访问资源或资源池的线程数量。

所提到的文档中就有一个示例,说明如何限制将执行操作的最大并发任务数:

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
    private static SemaphoreSlim semaphore;
    // A padding interval to make the output more orderly.
    private static int padding;

    public static void Main()
    {
        // Create the semaphore.
        semaphore = new SemaphoreSlim(0, 3);
        Console.WriteLine("{0} tasks can enter the semaphore.",
                          semaphore.CurrentCount);
        Task[] tasks = new Task[5];

        // Create and start five numbered tasks.
        for (int i = 0; i <= 4; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                // Each task begins by requesting the semaphore.
                Console.WriteLine("Task {0} begins and waits for the semaphore.",
                                  Task.CurrentId);
                
                int semaphoreCount;
                semaphore.Wait();
                try
                {
                    Interlocked.Add(ref padding, 100);

                    Console.WriteLine("Task {0} enters the semaphore.", Task.CurrentId);

                    // The task just sleeps for 1+ seconds.
                    Thread.Sleep(1000 + padding);
                }
                finally {
                    semaphoreCount = semaphore.Release();
                }
                Console.WriteLine("Task {0} releases the semaphore; previous count: {1}.",
                                  Task.CurrentId, semaphoreCount);
            });
        }

        // Wait for half a second, to allow all the tasks to start and block.
        Thread.Sleep(500);

        // Restore the semaphore count to its maximum value.
        Console.Write("Main thread calls Release(3) --> ");
        semaphore.Release(3);
        Console.WriteLine("{0} tasks can enter the semaphore.",
                          semaphore.CurrentCount);
        // Main thread waits for the tasks to complete.
        Task.WaitAll(tasks);

        Console.WriteLine("Main thread exits.");
    }
}
// The example displays output like the following:
//       0 tasks can enter the semaphore.
//       Task 1 begins and waits for the semaphore.
//       Task 5 begins and waits for the semaphore.
//       Task 2 begins and waits for the semaphore.
//       Task 4 begins and waits for the semaphore.
//       Task 3 begins and waits for the semaphore.
//       Main thread calls Release(3) --> 3 tasks can enter the semaphore.
//       Task 4 enters the semaphore.
//       Task 1 enters the semaphore.
//       Task 3 enters the semaphore.
//       Task 4 releases the semaphore; previous count: 0.
//       Task 2 enters the semaphore.
//       Task 1 releases the semaphore; previous count: 0.
//       Task 3 releases the semaphore; previous count: 0.
//       Task 5 enters the semaphore.
//       Task 2 releases the semaphore; previous count: 1.
//       Task 5 releases the semaphore; previous count: 2.
//       Main thread exits.
© www.soinside.com 2019 - 2024. All rights reserved.