Reputation: 201
I have a method called DownloadFileAsync(url, Func<RemoteFileResponse, Task> onDownloadFinished)
which performs the following:
This feels dirty to me as I need to perform the download on a background thread but I can't await it because I want to be able to return the cached file right away. The problem is that I lose any exception context if I do this.
Some options I can think of:
Was wondering if anyone had any other suggestions of how I could do this?
Pseudo code for my method:
Task<IFile> async DownloadFileAsync(url, Func<RemoteFileResponse, Task> onDownloadFinished)
{
var cache = await CheckCacheAsync(url);
// don't await this so the callee can use the cached file right away
// instead return the download result on the download finished callback
DownloadUrlAndUpdateCache(url, onDownloadFinished);
return cache
}
Upvotes: 3
Views: 684
Reputation: 1389
I would suggest to create a new class FileRetriever that would contains the cached file and a Task that would be completed when the latest revision of the file is retrieved from the server.
Something like this:
public class FileRetriever
{
public IFile CachedFile { get; private set; }
// Indicate if the CachedFile is the latest version of the file. If not,
// then LatestFileTask will complete eventually with the latest revision
public bool IsLatestFileVersion { get; private set; }
public Task<IFile> LatestFileTask { get; private set; }
public FileRetriever(IFile file)
{
IsLatestFileVersion = true;
CachedFile = file;
LatestFileTask = Task.FromResult(file);
}
public FileRetriever(IFile file, Task<IFile> latestFileTask)
{
IsLatestFileVersion = false;
CachedFile = file;
LatestFileTask = latestFileTask;
}
}
To initialize the FileRetriever object, you would first check your cache to see if the file is already present.
Task<FileRetriever> async GetFileRetrieverAsync(url)
{
IFile file = GetFileFromCache();
if (file == null)
{
// File is not present in cache. Download it asynchronously and await it
// before returning the FileRetriever
IFile file = await DownloadUrlAndUpdateCacheAsync(url);
return new FileRetriever(file);
}
// File is present in cache but a new revision might be present on server.
// Start the download task but don't await for its completion.
Task<IFile> task = DownloadUrlAndUpdateCacheAsync(url);
return new FileRetriever(file, task);
}
And then, you would use it like this:
var fileRetriever = await GetFileRetrieverAsync(url);
DoSomethingWithFile(fileRetriever.CachedFile);
if (!fileRetriever.IsLatestFileVersion)
{
// A new revision of the file might be present on the server.
// Wait for the download before updating the UI with the new file revision
fileRetriever.LatestFileTask.ContinueWith(t => DoSomethingWithFile(t.Result));
}
Please note that I didn't tested this and error handling should be added in case where the download of the file fails.
Upvotes: 0
Reputation: 457472
If your cache is an in-memory cache, then it's easier to cache the tasks rather than their results:
ConcurrentDictionary<Url, Task<IFile>> cache = ...;
Task<IFile> DownloadFileAsync(url) // no async keyword
{
return cache.GetOrAdd(url, url => DownloadUrlAsync(url));
}
private async Task<IFile> DownloadUrlAsync(url)
{
... // actual download
}
Logically, the GetOrAdd
is doing this (but in a thread-safe and more efficient manner):
if (cache.ContainsKey(url))
return cache[url];
cache[url] = DownloadUrlAsync(url);
return cache[url];
Note, however, that this will cache the complete task, so download exceptions are also cached.
Upvotes: 3