当前位置: 动力学知识库 > 问答 > 编程问答 >

.net - How to Read/Generate+Read file in thread-safe way in C#

问题描述:

I'm using .NET Framework v4.5.

I'm creating kind of image resizer by using MagickImage library.


Use case:

User uploads big image (4k*4k pixels) and use it in different places with different size (200*200 pixels, 1200*1200 pixels).

So I'm generating such images on demand by resizing big one and storing them on the disk.

Case with concurrency: User upload image, and then couple of users request the thumbnail of this image. In that moment each of user requests starts to create resized thumbnail because it is not exists yet. When the first thread which finished resizing save it to disk. All another threads will get exception because of the file is already in use.


Before that it was working with single thread and there was no need for a thread safety.

But now its gonna be used in a web-based project, and concurrent requests are possible as well.

Current implementation is looks like:

if (!FileExists(cachedImageFilepath))

{

byte[] resizedImage = _imageResizer.ResizeImage(originalImageFilepath, width, height);

_physicalFileManager.WriteToFile(cachedImageFilepath, resizedImage);

}

return cachedImageFilepath;

The easies way is simply to use lock around this operation but in this case Resizer will resize only one image in single period of time.

Another variant that I see is to create something like a lock mechanism which will lock by string key.

But anyway I see a problem with double checking if the file exists after releasing lock like:

if (!FileExists(cachedImageFilepath)){

lock(lockObjects[lockKey]){

if (!FileExists(cachedImageFilepath)){

}

}

}

Is there a good way or even .NET mechanism to do such thing without overhead?

网友答案:

It seems like what you need is a thread-safe thumbnail manager. I.e. a class that itself understands how to coordinate access to the files.

A simple version of that might look like this:

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;
}

In other words, first the manager checks to see if it knows about the necessary file. If it does, then that means the file's already been created and it just returns the path.

If it doesn't, then the next thing it checks is to see whether the creation of the necessary file is already in progress. After all, no point in making the same file more than once! If it's not already in progress, then it starts a Task to make the file. If it already is in progress, then it simply retrieves the Task representing that operation.

In either case, the Task representing the operation is awaited. The method returns at that point; when the operation is done, the method resumes execution, adding the name of the resulting file to the dictionary of completed files, and removing the completed task from the in-progress dictionary.

Naturally, it being an async method, the correct way for the caller to use this is for it itself to use await when calling it, so that the method can complete asynchronously if needed without blocking the calling thread. There's not enough context in your question to know exactly what that would look like, but I presume you can figure that part out.

分享给朋友:
您可能感兴趣的文章:
随机阅读: