Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 523
Dmitry Zaets Avatar asked Feb 12 '15 14:02

Dmitry Zaets


People also ask

Can two threads read the same file C?

Multiple threads can also read data from the same FITS file simultaneously, as long as the file was opened independently by each thread. This relies on the operating system to correctly deal with reading the same file by multiple processes.

Is printf () thread-safe?

the standard C printf() and scanf() functions use stdio so they are thread-safe.

Is read () thread-safe?

If a single thread calling read never processes the same data twice, then read is thread safe if two threads calling it also never process the same data twice.

Is C write thread-safe?

C functions are generally thread-safe unless they access some common data (i.e. global or static variables).


1 Answers

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.

like image 69
Peter Duniho Avatar answered Oct 15 '22 04:10

Peter Duniho