启动后台任务并立即返回
本文关键字:返回 后台任务 启动 | 更新日期: 2023-09-27 18:12:11
我有一个名为DownloadFileAsync(url, Func<RemoteFileResponse, Task> onDownloadFinished)
的方法,它执行以下操作:
- 检查缓存,如果找到,立即返回缓存,并启动后台任务查看缓存是否需要更新
- 如果没有找到,返回null并启动后台任务异步下载文件
- 文件下载后,它回调onDownloadFinished处理程序。
这对我来说感觉很脏,因为我需要在后台线程上执行下载,但我不能等待它,因为我希望能够立即返回缓存的文件。问题是,如果我这样做,我将失去任何异常上下文。
我能想到的一些选项:
- 使用IProgress接口返回缓存文件,然后在下载完成后返回下载结果。
- 将方法分为两个调用(一个获取缓存文件)另一个下载/更新(不是首选的方式,因为我想保持我的接口到一个方法)。
我想知道是否有人有任何其他的建议,我怎么才能做到这一点?
我的方法的伪代码: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
}
如果您的缓存是内存缓存,那么更容易缓存任务而不是它们的结果:
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
}
逻辑上,GetOrAdd
正在这样做(但以线程安全和更有效的方式):
if (cache.ContainsKey(url))
return cache[url];
cache[url] = DownloadUrlAsync(url);
return cache[url];
但是,请注意,这将缓存完整的任务,因此下载异常也会被缓存。
我建议创建一个新的类FileRetriever,它将包含缓存的文件和一个任务,该任务将在从服务器检索到文件的最新版本时完成。
像这样:
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;
}
}
要初始化FileRetriever对象,首先要检查缓存,看看文件是否已经存在。
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);
}
然后,你可以这样使用:
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));
}
请注意,我没有测试过这个,如果文件下载失败,应该添加错误处理。