我正在开发一个ASP.NET应用程序,它可以对大图片文件(最高100MP!)进行一些成像操作。这些图片的处理实际上相对较快(不到一秒),但内存使用量可以理解为巨大(每张图像大约500MB)。当多个图片同时上传到服务器时,服务器开始同时接受所有请求,并且主机内存不足。
1)如何最大限度地减少这种内存影响? 2)如果内存影响最小化,仍然会有一个限制。那么,我是否还可以限制同时处理的图像的绝对数量?
我自己的想法和想法......
因为执行时间允许一些等待(如果请求需要几秒钟就没问题)我想通过排队图像转换函数来解决这个问题,并且只允许同时执行多达2或3张图片时间。这样,内存使用量约为1.5GB,这很好。在转向生产时,我想增加这个数字,因为那里有更多的内存。
也许:我如何应用C#多线程类(例如ConcurrentQueue,BlockingCollection,Interlocked)来确保ASP.NET请求处理程序调用的单个方法只能并行执行有限数量的实例?
请注意,昂贵的线程操作在这里并不是真正的问题,因为与转换图像的第二次操作相比,开销可以忽略不计。
public ActionResult UploadLargePicture()
{
// Some trivial stuff like authorization
var result = VeryMemoryIntensiveFunction(); // This is the part of the code that should have limited concurrency
return Json(...);
}
答案 0 :(得分:2)
1)减少对图像处理的内存需求
您的最终问题是由于内存限制而无法处理并发请求。
我怀疑你使用.Net函数来操作源图像。考虑以不同方式加载图像,以便它不会缓存在内存中。处理可能(或可能不)花费更长时间,但它比构建排队功能更简单可靠。
而不是
Bitmap bmp = new Bitmap(pathToFile);
使用
var bmp = (Bitmap)Image.FromStream(sourceFileStream, false, false);
请参阅https://stackoverflow.com/a/47424968/887092
仍然存在限制:根据系统的使用方式,您仍可能需要限制并发请求的数量。
2)内存减少应该足够了,但这里有一些额外的方法来保持并发限制
选项A)拒绝服务器是否过载。
当每个请求到达时,跟踪已经运行的当前请求的数量。如果它高于您定义的限制,则在RequestResponse中返回错误。在这种情况下,您可能应该使用HTTP状态代码429 - Too Many Requests
。
try
{
var currentImageCounter = Interlocked.Increment(ref imageCounter);
if (currentImageCounter > 3)
{
throw new Exception(""); //Should be caught and result in HttpResponse.Status = 429
}
//Image processing code here
}
finally
{
Interlocked.Decrement(ref ImageCounter);
}
选项A最适合响应速度,但如果超载,用户将收到错误消息。
选项B:数据库中的队列
当每个新图像到达时,将其保存到磁盘,并将记录添加到数据库,然后触发批处理。
有一个批处理进程(控制台),每隔X秒或触发时检查表是否有不完整的工作(可以是localhost HTTP请求)。确保批处理一次只运行一个实例(使用命名的Mutex / Semaphore)。
选项B缩放最多,但没有快速响应
选项C:A和B的组合
而不是在达到阈值时拒绝(3),这些应该在数据库中排队作为后备。
选项D:我之前写的选项B的更详细的实现
答案 1 :(得分:0)
我最终采用了一些使用一些多线程结构的集成方法。请注意,如果您的项目不是必须立即处理图像(即请求不等待内存密集型功能),那么@Todd应答是可行的方法。下面的解决方案可以工作,但是当同时上传大量图像时,可以增加超出请求超时限制的等待时间。
using System;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Threading;
namespace MyApp.Services
{
/// <summary>
/// This service is meant to allow for scheduling memory intensive tasks,
/// a maximum number of these types of tasks is defined, scheduling via
/// this service guarantees no more than the max number of tasks are executed
/// at once.
/// </summary>
public class MemoryIntensiveTaskService
{
private class ExecutionObject
{
public Func<object> Task { get; set; }
public AutoResetEvent Event { get; set; }
public object Result { get; set; }
public T CastResult<T>()
{
return (T)Result;
}
}
private static readonly int MaxConcurrency = 2;
private static BlockingCollection<ExecutionObject> _queue = new BlockingCollection<ExecutionObject>();
static MemoryIntensiveTaskService()
{
// Load MaxConcurrency number of consumers
for (int i = 0; i < MaxConcurrency; i++)
Task.Factory.StartNew(Consumer);
}
public T ScheduleTaskAndWait<T>(Func<T> action)
{
var executionObject = new ExecutionObject
{
Task = () => action(),
Event = new AutoResetEvent(false),
Result = null
};
// Add item to queue, will be picked up ASAP by a
// consumer
_queue.Add(executionObject);
// Wait for completion
executionObject.Event.WaitOne();
return executionObject.CastResult<T>();
}
private static void Consumer()
{
while (true)
{
var executionObject = _queue.Take();
// Execute task, store result
executionObject.Result = executionObject.Task();
// Fire event to signal to producer that execution
// has finished
executionObject.Event.Set();
}
}
}
}