Multithreaded document caching

We have a Windows Service that is using GenerateHTML method to load the document cache so users do not have to wait for the Viewer to render the documents.


I am trying to change this to be multithreaded and I am running into many issues. Have you ever used the GenerateHtml in a multithreaded scenario?

Using Parallel.Foreach I tried instantiating an object for each thread. If I do that I receive the following error for this code:
Parallel.ForEach()
{
////custom code
DocumentCache dc = new DocumentCache(null, storageLocation);
dc.GenerateHtml(documentPath, “our url”, false, false);
///custom code
}

ERROR DocumentPreviewCache - failed for file: 910600 Exception has been thrown by the target of an invocation. at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at (Object , Object[] )
at ..(Stream stream, Boolean forceApply)
at Groupdocs.Web.UI.DocumentCache…ctor(String licensePath, String rootStoragePath, String workingDirectoryPath)
at ControleEngine.DocumentPreviewCache.ProcessDocumentCache(FolderDetail file, Boolean rewrite) in DocumentPreviewCache.cs:line 210

If I run that same code with the Parallel option of 1 max thread it runs fine.

I changed the code to use a global DocumentCache object and it seemed to be working but after a few hours it became very slow and eventually the app crashed.
DocumentCache _dc = new DocumentCache();
Parallel.ForEach()
{
////custom code
_dc.GenerateHtml(documentPath, “our url”, false, false);
///custom code
}

Description: The process was terminated due to an unhandled exception.
Exception Info: System.ObjectDisposedException
Stack:
at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean ByRef)
at Microsoft.Win32.Win32Native.SetEvent(Microsoft.Win32.SafeHandles.SafeWaitHandle)
at System.Threading.EventWaitHandle.Set()
at System.Runtime.Remoting.Messaging.AsyncResult.SyncProcessMessage(System.Runtime.Remoting.Messaging.IMessage)
at System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage(System.Runtime.Remoting.Messaging.IMessage, System.Runtime.Remoting.Messaging.IMessageSink)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()

Any thoughts on using your API in this scenario? Thanks

Hello Kevitra,


Thank you for your request. Unfortunately we never tested the DocumentCache class with multithreaded scenario. The second approach is looking good but you should add old cache cleaning with RemoveOldEntries(TimeSpan olderThan, string logFilePath = null) and also check the used memory could be that the applications simply using all the memory.

Best regards.

We have tried using the second method with a global object but after about an hour it consumes 4-5GB of memory and eventually it crashes.

We are still in the evaluation phase of using your API. Can you provide any insight on if it is possible to create and destroy the object for each file so the memory is garbage collected? If I try running the API with multiple copies of the object I receive the errors I listed in the original post.

Thanks

Hi,


To dispose the data and clean the memory you can implement such workaround:
class MyDoc : IDisposable
{
string licensePath;
string rootStoragePath;
string workingDirectoryPath = null;

public MyDoc(string licensePath, string rootStoragePath, string workingDirectoryPath = null)
{
this.licensePath = licensePath;
this.rootStoragePath = rootStoragePath;
this.workingDirectoryPath = workingDirectoryPath;
}

public void Generate(string documentPath, string baseUrl, bool usePngImagesForHtmlBasedEngine, bool useHttpHandlers = true, bool embedImagesIntoHtmlForWordFiles = false, bool supportListOfContentControls = false) {
DocumentCache dc = new DocumentCache(this.licensePath, this.rootStoragePath, this.workingDirectoryPath);
dc.GenerateHtml(“candy.pdf”, System.Web.HttpRuntime.AppDomainAppVirtualPath, false, false);
}

public void Dispose() {
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
}
protected void Page_Load(object sender, EventArgs e)
{
using (MyDoc dc = new MyDoc(null, Server.MapPath("~/testfiles/")))
{
dc.Generate(“candy.pdf”, System.Web.HttpRuntime.AppDomainAppVirtualPath, false, false);
}
}

As you can see this code will create a IDisposable class “MyDoc” which call DocumentCache class for generating the cache and then it can be cleaned with garbage collector.

Best regards.