Implement thread-safe cache for GroupDocs.Viewer
Leave feedback
This page describes how to develop a thread-sage cache using the C# lock statement and the ConcurrentDictionary<,> class.
Introduction
A method is thread-safe if multiple threads can call it without breaking the functionality. Achieving thread safety is a complex task, so general-purpose classes are usually not thread-safe. The most common way to achieve thread safety is to lock the resource for exclusive use by a single thread at any given time.
Issue
You need to develop a web application where multiple users can simultaneously view the same file. The web application uses GroupDocs.Viewer on the server side. You have to ensure that multiple threads can safely read and write to the cache.
In GroupDocs.Viewer, you can use caching to improve the performance if the same document is processed multiple times (read more about caching here.) The FileCache class is an implementation of the ICache interface that uses a local disk to store the cache files. The FileCache is not thread safe, so you need to make it so.
Solution
The FileCache class uses a local disk to read and write output files. You need to implement thread safe reading and writing to disk. To do this, use the list to store the key or the file ID and associated object you need to lock. The simplest way is to use the ConcurrentDictionary<,> class of the .NET Framework 4.0. The ConcurrentDictionary is a thread safe implementation of a dictionary of key-value pairs. Implement the ThreadSafeCache class that wraps around not thread safe class that implements the ICache interface.
All the ThreadSafeCache class methods use locks to make calls thread safe. The ConcurrentDictionaryKeyLockerStore class uses ConcurrentDictionary to create the locker object or to retrieve it if it already exists. It also creates a unique key that identifies a cached file.
usingSystem.IO;usingSystem.Collections.Generic;usingSystem.Collections.Concurrent;usingGroupDocs.Viewer;usingGroupDocs.Viewer.Caching;usingGroupDocs.Viewer.Interfaces;usingGroupDocs.Viewer.Options;namespaceThreadSaveCacheExample{staticclassProgram{privatestaticreadonlyConcurrentDictionary<string,object>KeyLockerMap=newConcurrentDictionary<string,object>();staticvoidMain(){stringfileName="sample.pdf";stringcacheFolder=fileName.Replace('.','_');stringcachePath=Path.Combine("cache",cacheFolder);stringuniqueKeyPrefix=cachePath;ICachefileCache=newFileCache(cachePath);IKeyLockerStorekeyLockerStore=newConcurrentDictionaryKeyLockerStore(KeyLockerMap,uniqueKeyPrefix);ICachethreadSafeCache=newThreadSafeCache(fileCache,keyLockerStore);ViewerSettingsviewerSettings=newViewerSettings(threadSafeCache);List<MemoryStream>pages=newList<MemoryStream>();using(Viewerviewer=newViewer(fileName,viewerSettings)){IPageStreamFactorypageStreamFactory=newMemoryPageStreamFactory(pages);ViewOptionsviewOptions=HtmlViewOptions.ForEmbeddedResources(pageStreamFactory);viewer.View(viewOptions);}}}classThreadSafeCache:ICache{privatereadonlyICache_cache;privatereadonlyIKeyLockerStore_keyLockerStore;publicThreadSafeCache(ICachecache,IKeyLockerStorekeyLockerStore){_cache=cache;_keyLockerStore=keyLockerStore;}publicvoidSet(stringkey,objectvalue){lock(_keyLockerStore.GetLockerFor(key)){_cache.Set(key,value);}}publicboolTryGetValue<TEntry>(stringkey,outTEntryvalue){lock(_keyLockerStore.GetLockerFor(key)){return_cache.TryGetValue(key,outvalue);}}publicIEnumerable<string>GetKeys(stringfilter){lock(_keyLockerStore.GetLockerFor("get_keys")){return_cache.GetKeys(filter);}}}interfaceIKeyLockerStore{objectGetLockerFor(stringkey);}classConcurrentDictionaryKeyLockerStore:IKeyLockerStore{privatereadonlyConcurrentDictionary<string,object>_keyLockerMap;privatereadonlystring_uniqueKeyPrefix;publicConcurrentDictionaryKeyLockerStore(ConcurrentDictionary<string,object>keyLockerMap,stringuniqueKeyPrefix){_keyLockerMap=keyLockerMap;_uniqueKeyPrefix=uniqueKeyPrefix;}publicobjectGetLockerFor(stringkey){stringuniqueKey=GetUniqueKey(key);return_keyLockerMap.GetOrAdd(uniqueKey,k=>newobject());}privatestringGetUniqueKey(stringkey){return$"{_uniqueKeyPrefix}_{key}";}}classMemoryPageStreamFactory:IPageStreamFactory{privatereadonlyList<MemoryStream>_pages;publicMemoryPageStreamFactory(List<MemoryStream>pages){_pages=pages;}publicStreamCreatePageStream(intpageNumber){MemoryStreampageStream=newMemoryStream();_pages.Add(pageStream);returnpageStream;}publicvoidReleasePageStream(intpageNumber,StreampageStream){//Do not release page stream as we'll need to keep the stream open}}}
ImportsSystem.IOImportsSystem.Collections.ConcurrentImportsGroupDocs.ViewerImportsGroupDocs.Viewer.CachingImportsGroupDocs.Viewer.InterfacesImportsGroupDocs.Viewer.OptionsImportsSystem.Runtime.InteropServicesModuleProgramPrivateReadOnlyKeyLockerMapAsConcurrentDictionary(OfString,Object)=NewConcurrentDictionary(OfString,Object)()PublicSubMain()DimfileNameAsString="resume.pdf"DimcacheFolderAsString=fileName.Replace("."c,"_"c)DimcachePathAsString=Path.Combine("cache",cacheFolder)DimuniqueKeyPrefixAsString=cachePathDimfileCacheAsICache=NewFileCache(cachePath)DimkeyLockerStoreAsIKeyLockerStore=NewConcurrentDictionaryKeyLockerStore(KeyLockerMap,uniqueKeyPrefix)DimthreadSafeCacheAsICache=NewThreadSafeCache(fileCache,keyLockerStore)DimviewerSettingsAsViewerSettings=NewViewerSettings(threadSafeCache)DimpagesAsList(OfMemoryStream)=NewList(OfMemoryStream)()UsingviewerAsViewer=NewViewer(fileName,viewerSettings)DimpageStreamFactoryAsIPageStreamFactory=NewMemoryPageStreamFactory(pages)DimviewOptionsAsViewOptions=HtmlViewOptions.ForEmbeddedResources(pageStreamFactory)viewer.View(viewOptions)EndUsingEndSubEndModuleClassThreadSafeCacheImplementsICachePrivateReadOnly_cacheAsICachePrivateReadOnly_keyLockerStoreAsIKeyLockerStorePublicSubNew(cacheAsICache,keyLockerStoreAsIKeyLockerStore)_cache=cache_keyLockerStore=keyLockerStoreEndSubPublicFunctionTryGetValue(OfTEntry)(keyAsString,<Out>ByRefvalueAsTEntry)AsBooleanImplementsICache.TryGetValueSyncLock_keyLockerStore.GetLockerFor(key)Return_cache.TryGetValue(key,value)EndSyncLockEndFunctionPublicFunctionICache_GetKeys(filterAsString)AsIEnumerable(OfString)ImplementsICache.GetKeysSyncLock_keyLockerStore.GetLockerFor("get_keys")Return_cache.GetKeys(filter)EndSyncLockEndFunctionPublicSubICache_Set(keyAsString,valueAsObject)ImplementsICache.[Set]SyncLock_keyLockerStore.GetLockerFor(key)_cache.Set(key,value)EndSyncLockEndSubEndClassInterfaceIKeyLockerStoreFunctionGetLockerFor(keyAsString)AsObjectEndInterfaceClassConcurrentDictionaryKeyLockerStoreImplementsIKeyLockerStorePrivateReadOnly_keyLockerMapAsConcurrentDictionary(OfString,Object)PrivateReadOnly_uniqueKeyPrefixAsStringPublicSubNew(keyLockerMapAsConcurrentDictionary(OfString,Object),uniqueKeyPrefixAsString)_keyLockerMap=keyLockerMap_uniqueKeyPrefix=uniqueKeyPrefixEndSubPublicFunctionGetLockerFor(keyAsString)AsObjectImplementsIKeyLockerStore.GetLockerForDimuniqueKeyAsString=Me.GetUniqueKey(key)Return_keyLockerMap.GetOrAdd(uniqueKey,Function(k)NewObject())EndFunctionPrivateFunctionGetUniqueKey(keyAsString)AsStringReturn$"{_uniqueKeyPrefix}_{key}"EndFunctionEndClassClassMemoryPageStreamFactoryImplementsIPageStreamFactoryPrivateReadOnly_pagesAsList(OfMemoryStream)PublicSubNew(pagesAsList(OfMemoryStream))_pages=pagesEndSubPublicSubIPageStreamFactory_ReleasePageStream(pageNumberAsInteger,pageStreamAsStream)ImplementsIPageStreamFactory.ReleasePageStream'Do not release page stream as we'll need to keep the stream open
EndSubPublicFunctionIPageStreamFactory_CreatePageStream(pageNumberAsInteger)AsStreamImplementsIPageStreamFactory.CreatePageStreamDimpageStreamAsMemoryStream=NewMemoryStream()_pages.Add(pageStream)ReturnpageStreamEndFunctionEndClass
Was this page helpful?
Any additional feedback you'd like to share with us?
Please tell us how we can improve this page.
Thank you for your feedback!
We value your opinion. Your feedback will help us improve our documentation.