2016-12-30 58 views
13

查看MemoryCache的文檔,我期望如果在過期期間內訪問了某個對象,則會刷新該期限。說實話,我認爲我從「滑動」這個名字推斷出任何東西。SlidingExpiration和MemoryCache

然而,似乎從這個測試

[Test] 
    public void SlidingExpiryNotRefreshedOnTouch() 
    { 
     var memoryCache = new MemoryCache("donkey") 
     { 
      { 
       "1", 
       "jane", 
       new CacheItemPolicy {SlidingExpiration = TimeSpan.FromSeconds(1) } 
      } 
     }; 
     var enumerable = Enumerable.Repeat("1", 100) 
      .TakeWhile((id, index) => 
      { 
       Thread.Sleep(100); 
       return memoryCache.Get(id) != null; // i.e. it still exists 
      }) 
      .Select((id, index) => (index+2)*100.0/1000); // return the elapsed time 
     var expires = enumerable.Last(); // gets the last existing entry 
     expires.Should().BeGreaterThan(1.0); 
    } 

它未能並表現出該物體被排出,一旦時間跨度是完整的對象是否已經被訪問的行爲。 Linq查詢在enumerable.Last()中執行;聲明,此時只會在緩存未過期時纔會執行。一旦它停止,列表中的最後一項將指示該項目在緩存中所處的時間。

對於清晰度這個問題是關於MemoryCache的行爲。不是linq查詢。

這是其他人的期望嗎(即過期每次觸摸都不會滑動)? 是否存在延長「觸摸」對象的生命期的模式?

更新我發現,即使我寫了一個包裝緩存周圍並重新添加的對象回到我每次檢索它的時候緩存,與另一SlidingExpiration它仍然只是兌現的初始設置。爲了以我想要的方式工作,我必須在重新添加它之前將它從緩存中物理刪除!這可能會在多線程環境中導致不合需要的競爭條件。

+0

也許你想把這個問題分成兩半? 「MemoryCache」使用情況或Linq使用情況有問題嗎? –

回答

18
... new CacheItemPolicy {SlidingExpiration = TimeSpan.FromSeconds(1) } 

這在MSDN中沒有充分記錄。你有點不吉利,1秒是不夠的。通過頭髮,使用2秒鐘,你會發現它的工作就像你希望的那樣。用FromMilliseconds()修補一些,你會發現在這個程序中約1.2秒是最低的。

解釋這是相當複雜的,我不得不談論MemoryCache如何避免每次訪問緩存時必須更新滑動計時器。正如你想象的那樣,這相對昂貴。讓我們走一條捷徑,帶您到相關Reference Source code。小到足以粘貼在這裏:

internal void UpdateSlidingExp(DateTime utcNow, CacheExpires expires) { 
     if (_slidingExp > TimeSpan.Zero) { 
      DateTime utcNewExpires = utcNow + _slidingExp; 
      if (utcNewExpires - _utcAbsExp >= CacheExpires.MIN_UPDATE_DELTA || utcNewExpires < _utcAbsExp) { 
       expires.UtcUpdate(this, utcNewExpires); 
      } 
     } 
    } 

CacheExpires.MIN_UPDATE_DELTA是問題的關鍵,它可以防止UtcUpdate()被調用。或者換句話說,至少需要MIN_UPDATE_DELTA時間才能更新滑動計時器。該CacheExpired類不被參考源,一個暗示,他們並不完全感到高興它的工作方式索引:)但一個像樣的反編譯器可以告訴你:

static CacheExpires() 
{ 
    MIN_UPDATE_DELTA = new TimeSpan(0, 0, 1); 
    MIN_FLUSH_INTERVAL = new TimeSpan(0, 0, 1); 
    // etc... 
} 

換句話說,硬編碼爲1第二。現在無法改變它,這非常難看。這個測試程序中的SlidingExpiration值需要大約1.2秒的時間,因爲Thread.Sleep(100)實際上並沒有在100毫秒內進入睡眠狀態,這需要多一點時間。換句話說,它將是第11次Get()調用,它使滑動計時器在此測試程序中滑動。你沒有那麼遠。

那麼,這應該被記錄,但我想這可能會改變。目前,您需要假設滑動到期時間應至少爲2秒。

+1

這是一個很好的答案,並且是stackoverflow功能的一個很好的例子。我知道1s的時間跨度並不是真正的代表緩存示例,但顯然希望儘量減少測試運行的時間。我還使用了Timer和StopWatch來測試相同的事情(沒有200ms滯後和停止條件)但是認爲包含的例子是最簡單的展示問題。基本上任何> 1s 10ms的工作如我所料。 – Yoztastic