C# 启动后台任务并立即返回
我有一个名为C# 启动后台任务并立即返回,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我有一个名为DownloadFileAsync(url,Func-ondownloadfished)的方法,它执行以下操作: 检查缓存,如果找到,立即返回缓存并启动后台任务以查看是否需要更新缓存 如果未找到,则返回null并启动后台任务以异步下载文件 下载文件后,它会调用onDownloadFinished处理程序 这让我感觉很脏,因为我需要在后台线程上执行下载,但我不能等待它,因为我希望能够立即返回缓存文件。问题是,如果这样做,我会丢失任何异常上下文 我能想到的一些选择: 使用IProg
DownloadFileAsync(url,Func-ondownloadfished)
的方法,它执行以下操作:
- 检查缓存,如果找到,立即返回缓存并启动后台任务以查看是否需要更新缓存
- 如果未找到,则返回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
}
Task async DownloadFileAsync(url,Func-onDownloadFinished)
{
var cache=await-CheckCacheAsync(url);
//不要等待,这样被调用方就可以立即使用缓存文件
//而是在下载完成回调上返回下载结果
DownloadUrl和UpdateCache(url,onDownloadFinished);
返回缓存
}
如果您的缓存是内存缓存,那么缓存任务比缓存结果更容易:
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
}
但是,请注意,这将缓存完整的任务,因此下载异常也会被缓存。我建议创建一个新的类FileRetriever,该类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;
}
}
请注意,我没有对此进行测试,如果文件下载失败,则应添加错误处理。请包括
DownloadFileAsync(url,Func onDownloadFinished)的返回类型。
只返回任务
,而不是使用回调委托。@ScottChamberlain我用伪代码更新了问题,说明了我正在尝试做什么。@JustinHorst该方法只能返回一个任务
。如果该项被缓存,那么该任务将很快完成并具有缓存值。如果它没有被缓存,那么完成任务只需要更长的时间。调用者不需要知道或关心值是否来自缓存,就像编写同步方法时设置的那样。@JustinHorst然后总是返回两个任务,一个用于缓存版本,一个用于未缓存版本,而不是一个任务和一个回调。Stephen感谢您的响应!然而,我有以下要求。即使结果在缓存中,我仍然希望发出webrequest以查看我拥有的版本是否仍然是新的(即,我在下一个请求中发送Etag,如果图像不再是新的,则重新下载图像)。另外,我的缓存在另一个服务中(不在内存中)。@Stephen,我认为最好将DownloadUrlAsync(url)
包装成new Lazy(()=>DownloadUrlAsync(url))
,否则在使用同一键的并发调用期间,ConcurrentDictionary.GetOrAdd的值工厂将被计算多次
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;
}
}
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));
}