2014-12-03 54 views
3

我有一个简单的MVC控制器,我做了异步,它使用await Task.WhenAll从Web服务获取数据。我想缓存这些调用的结果,所以我不必一直调用API。目前,当我得到响应时,我在视图控制器中缓存结果,但理想情况下,我希望调用该API的方法来处理缓存。问题在于该方法无法访问结果,因为它是异步的,只是返回一个任务。从ASP.NET中的异步方法缓存结果

是否有可能有另一种方法缓存结果后,他们返回?

public async Task<ActionResult> Index() 
{ 
    // Get data asynchronously 
    var languagesTask = GetDataAsync<List<Language>>("languages"); 
    var currenciesTask = GetDataAsync<List<Currency>>("currencies"); 

    await Task.WhenAll(languagesTask, currenciesTask); 


    // Get results 
    List<Language> languages = languagesTask.Result; 
    List<Currency> currencies = currenciesTask.Result; 


    // Add results to cache 
    AddToCache("languages", languages); 
    AddToCache("currencies", currencies); 


    // Add results to view and return 
    ViewBag.languages = languages; 
    ViewBag.currencies = currencies; 

    return View(); 
} 

public async Task<T> GetDataAsync<T>(string operation) 
{ 
    // Check cache for data first 
    string cacheName = operation; 

    var cacheData = HttpRuntime.Cache[cacheName]; 

    if (cacheData != null) 
    { 
     return (T)cacheData; 
    } 


    // Get data from remote api 
    using (HttpClient client = new HttpClient()) 
    { 
     client.BaseAddress = new Uri("https://myapi.com/"); 

     var response = await client.GetAsync(operation); 

     // Add result to cache 
     //... 

     return (await response.Content.ReadAsAsync<T>()); 
    } 
} 
+0

https://stackoverflow.com/questions/31831860/async-threadsafe-get-from-memorycache – 2017-07-08 14:26:12

回答

8

只要你的缓存实现的内存,你可以缓存任务本身,而不是任务结果:

public Task<T> GetDataAsync<T>(string operation) 
{ 
    // Check cache for data first 
    var task = HttpRuntime.Cache[operation] as Task<T>; 
    if (task != null) 
    return task; 

    task = DoGetDataAsync(operation); 
    AddToCache(operation, task); 
    return task; 
} 

private async Task<T> DoGetDataAsync<T>(string operation) 
{ 
    // Get data from remote api 
    using (HttpClient client = new HttpClient()) 
    { 
    client.BaseAddress = new Uri("https://myapi.com/"); 
    var response = await client.GetAsync(operation); 
    return (await response.Content.ReadAsAsync<T>()); 
    } 
} 

这种方法还有一个额外的好处,即如果多个HTTP请求试图获得相同的数据,他们实际上会共享任务本身。所以它共享实际的异步操作而不是结果。

但是,这种方法的缺点是Task<T>不是可序列化的,所以如果您使用自定义磁盘备份缓存或共享缓存(例如Redis),那么这种方法将无法工作。

+0

感谢您的回应。这看起来是一个好的解决方案。我正在使用内存中缓存,因此将测试缓存任务并查看是否适合该应用程序。 – markvpc 2014-12-03 14:08:29

0

我建议你使用MemoryCache

MemoryCache cache = MemoryCache.Default; 
string cacheName = "mycachename"; 

if cache.Contains(cacheName) == false || cache[cacheName] == null) 
{ 
    // load data 
    var data = await response.Content.ReadAsAsync<T>(); 
    // save data to cache 
    cache.Set(cacheName, data, new CacheItemPolicy() { SlidingExpiration = DateTime.Now.AddDays(1).TimeOfDay }); 
} 

return cache[cacheName]; 
+0

你错过了问题,如何的importat部分缓存异步方法的结果。你能否把这个添加到你的例子中? – user2900970 2014-12-03 12:51:02

+0

为什么异步使缓存任何不同?它不imho。这是在例子中。 – 2014-12-03 12:58:39

+0

你说得对,我没有仔细阅读。我猜你的解决方案应该可行。 – user2900970 2014-12-03 13:23:14

1

有点迟到回答这个问题,但我想我可以改善史蒂文斯答案与开放源代码库称为LazyCache这将在几行代码中为你做到这一点。它最近更新为处理缓存任务在内存中的这种情况。它也可用于nuget。

鉴于你获取数据的方法是像这样:

private async Task<T> DoGetDataAsync<T>(string operation) 
{ 
    // Get data from remote api 
    using (HttpClient client = new HttpClient()) 
    { 
    client.BaseAddress = new Uri("https://myapi.com/"); 
    var response = await client.GetAsync(operation); 
    return (await response.Content.ReadAsAsync<T>()); 
    } 
} 

那么你的控制器将成为

public async Task<ActionResult> Index() 
{ 
    // declare but don't execute a func unless we need to prime the cache 
    Func<Task<List<Language>>> languagesFunc = 
     () => GetDataAsync<List<Currency>>("currencies");  

    // get from the cache or execute the func and cache the result 
    var languagesTask = cache.GetOrAddAsync("languages", languagesFunc); 

    //same for currencies 
    Func<Task<List<Language>>> currenciesFunc = 
     () => GetDataAsync<List<Currency>>("currencies"); 
    var currenciesTask = cache.GetOrAddAsync("currencies", currenciesFunc); 

    // await the responses from the cache (instant) or the api (slow) 
    await Task.WhenAll(languagesTask, currenciesTask); 

    // use the results 
    ViewBag.languages = languagesTask.Result; 
    ViewBag.currencies = currenciesTask.Result; 

    return View(); 
} 

它内置了默认情况下锁定,因此缓存的方法才会每次缓存未命中执行一次,它使用lamda,因此您可以一次完成“获取或添加”。它默认为滑动过期20分钟,但你可以设置你喜欢的任何缓存策略。

关于缓存任务的更多信息在api docs中,您可能会发现sample webapi app to demo caching tasks有用。

(声明:我是LazyCache的作者)