【问题标题】:kick off background task and return immediately启动后台任务并立即返回
【发布时间】:2015-09-10 23:24:01
【问题描述】:

我有一个名为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
}

【问题讨论】:

  • 请包含DownloadFileAsync(url, Func&lt;RemoteFileResponse, Task&gt; onDownloadFinished)的返回类型
  • 只返回一个Task,而不是使用回调委托。
  • @ScottChamberlain 我用我正在尝试做的伪代码更新了这个问题。
  • @JustinHorst 该方法可以只返回一个Task。如果该项目被缓存,那么该任务将很快完成并具有缓存值。如果它没有被缓存,那么任务将需要更长的时间才能完成。调用者无需知道或关心该值是否来自缓存,就像您在编写同步方法时设置它一样。
  • @JustinHorst 然后总是返回两个任务,一个用于缓存版本,一个用于未缓存版本,而不是一个任务和一个回调。

标签: c# asynchronous async-await


【解决方案1】:

如果您的缓存是内存中的缓存,那么缓存任务比缓存结果更容易:

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];

但是请注意,这将缓存完整的任务,因此也会缓存下载异常。

【讨论】:

  • 斯蒂芬感谢您的回复!但是我有以下要求。即使结果在缓存中,我仍然想发出网络请求以查看我拥有的版本是否仍然新鲜(即,我在下一个请求中发送一个 Etag 并重新下载图像,如果它不再是新鲜的)。另外,我的缓存在不同的服务中(不在内存中)。
  • @Stephen,我认为最好将DownloadUrlAsync(url) 包装成new Lazy&lt;Task&lt;IFile&gt;&gt;(() =&gt; DownloadUrlAsync(url)),否则在使用相同键的并发调用期间,ConcurrentDictionary.GetOrAdd 的值工厂将被评估多次
【解决方案2】:

我建议创建一个新的 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)); 
}

请注意,我没有对此进行测试,如果文件下载失败,应添加错误处理。

【讨论】:

    猜你喜欢
    • 2012-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    • 2016-09-06
    • 2021-01-08
    • 1970-01-01
    • 2014-08-22
    相关资源
    最近更新 更多