2014-12-03 50 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的作者)