我正在使用 .NET Framework v4.5 。
我正在使用MagickImage库创建一种图像缩放器。
用户上传大图像(4k * 4k像素)并在不同大小(200 * 200像素,1200 * 1200像素)的不同位置使用。
所以我通过调整大图并将它们存储在磁盘上来按需生成这样的图像。
并发案例:用户上传图片,然后几个用户请求此图片的缩略图。在那一刻,每个用户请求开始创建已调整大小的缩略图,因为它尚不存在。完成调整大小的第一个线程将其保存到磁盘。由于该文件已被使用,所有其他线程都将获得异常。
在此之前它正在使用单线程,并且不需要线程安全。
但现在它将用于基于网络的项目,并发请求也是可能的。
目前的实施如下:
if (!FileExists(cachedImageFilepath))
{
byte[] resizedImage = _imageResizer.ResizeImage(originalImageFilepath, width, height);
_physicalFileManager.WriteToFile(cachedImageFilepath, resizedImage);
}
return cachedImageFilepath;
简单方法就是使用锁定此操作,但在这种情况下,Resizer将在一段时间内仅调整一个图像的大小。
我看到的另一个变体是创建类似锁定机制的东西,它将通过字符串键锁定。
但无论如何,我发现在释放锁之后,如果文件存在,请仔细检查是否存在问题:
if (!FileExists(cachedImageFilepath)){
lock(lockObjects[lockKey]){
if (!FileExists(cachedImageFilepath)){
}
}
}
有没有一种好方法甚至.NET机制可以在没有开销的情况下做这样的事情?
答案 0 :(得分:2)
看起来你需要的是一个线程安全的缩略图管理器。即一个本身就能理解如何协调对文件的访问的类。
简单版本可能如下所示:
class ThumbnailManager
{
private Dictionary<Tuple<string, int, int>, string> _thumbnails =
new Dictionary<Tuple<string, int, int>, string>();
private Dictionary<Tuple<string, int, int>, Task<string>> _workers =
new Dictionary<Tuple<string, int, int>, Task<string>>();
private readonly object _lock = new object();
public async Task<string> RetrieveThumbnail(string originalFile, int width, int height)
{
Tuple<string, int, int> key = Tuple.Create(originalFile, width, height);
Task task;
lock (_lock)
{
string fileName;
if (_thumbnails.TryGetValue(key, out fileName))
{
return fileName;
}
if (!_workers.TryGetValue(key, out task))
{
task = Task.Run(() => ResizeFile(originalFile, width, height));
_workers[key] = task;
}
}
string result = await task;
lock (_lock)
{
_thumbnails[key] = result;
_workers.Remove(key);
}
return result;
}
}
string ResizeFile(string originalImageFilepath, int width, int height)
{
string cachedImageFilepath = GenerateCachedImageFilepath(originalImageFilepath);
if (!FileExists(cachedImageFilepath))
{
byte[] resizedImage = _imageResizer.ResizeImage(originalImageFilepath, width, height);
_physicalFileManager.WriteToFile(cachedImageFilepath, resizedImage);
}
return cachedImageFilepath;
}
换句话说,首先管理员检查它是否知道必要的文件。如果是,则表示文件已经创建,只返回路径。
如果没有,那么它检查的下一件事是查看是否正在创建必要的文件。毕竟,没有必要多次制作同一个文件!如果它尚未进行,则会启动Task
来制作文件。如果它已在进行中,那么它只是检索表示该操作的Task
。
在任何一种情况下,都在等待代表该操作的Task
。该方法在该点返回;当操作完成后,该方法继续执行,将生成的文件的名称添加到已完成文件的字典中,并从正在进行的字典中删除已完成的任务。
当然,它是一个async
方法,调用者使用它的正确方法是在调用它时使用await
,这样方法可以在需要时异步完成而不会阻塞调用线程。你的问题中没有足够的背景来确切地知道它会是什么样子,但我认为你可以把这一部分弄清楚。