It seems like you need a thumbnail streaming manager. That is, a class that itself understands how to coordinate file access.
A simple version 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, the manager first checks to see if he knows about the file. If so, it means that the file has already been created and it simply returns the path.
If this is not the case, then the next thing he checks is to see if the necessary file is being created. In the end, there is nothing to do the same file more than once! If it is not already running, then it starts Task to make the file. If it is already running, it simply retrieves the Task representing this operation.
In either case, a Task representing the operation is expected. The method returns at this moment; when the operation is completed, the method resumes execution, adds the name of the received file to the dictionary of completed files and removes the completed task from the ongoing dictionary.
Naturally, this is an async method, the correct way for the caller to use it is for him to use await when calling, so that the method can be executed asynchronously, if necessary, without blocking the calling thread. There is not enough context in your question to know exactly how this will look, but I assume that you can understand this part.