All, I have a concurrency problem with a large application I have recently multi-threaded. The problem is within a script processor that can run batch jobs
public async Task<Result> ProcessScriptAsync(
CancellationTokenSource cancelSource,
TaskScheduler uiScheduler)
{
...
// Get instance of active workbook on UI thread.
IWorkbook workbook = this.workbookView.ActiveWorkbook;
while (notFinished)
{
...
Task<bool> runScriptAsyncTask = null;
runScriptAsyncTask = Task.Factory.StartNew<bool>(() =>
{
return RunScript(ref workbook);
}, this.token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
// Some cancellation support here...
// Run core asynchroniously.
try
{
bGenerationSuccess = await runScriptAsyncTask;
}
catch (OperationCanceledException)
{
// Handle cancellation.
}
finally
{
// Clean up.
}
}
...
}
My problem comes when you consider the RunScript
method. The object being passed in to RunScript
is not thread safe and was created on the UI thread. As such, I have to create a 'deep copy' of this object inside the RunScript
method...
private bool RunScript(ref IWorkbook workbook)
{
...
// Get a new 'shadow' workbook with which to operate on from a background thread.
IWorkbook shadowWorkbook;
if (File.Exists(workbook.FullName))
{
// This opens a workbook from disk. The Factory.GetWorkbook method is thread safe.
shadowWorkbook = SpreadsheetGear.Factory.GetWorkbook(workbook.FullName); // (##)
}
else
throw new FileNotFoundException(
"The current workbook is not saved to disk. This is a requirement. " +
"To facilitate multi-threading!");
// Do hard work here...
shadowWorkbook.WorkbookSet.GetLock();
try
{
// Do work and add worksheets to shadowWorkbook.
}
finally
{
// Reassign the UI workbook object to our new shadowWorkbook which
// has been worked on. This is fine to do as not work is actually being
// done on workbook.
workbook = shadowWorkbook;
shadowWorkbook.WorkbookSet.ReleaseLock();
}
}
My problem is on line marked by (##). Each time RunScript
is executed is creates a new shadowWorkbook
from disk. The problem with this is that some worksheets are created in the shadowWorkbook
which are subsequently copied back to workbook
at the end of the processing. However, each time I execute RunScript
I get the workbook from disk which does not have the new sheets generated in the last loop.
I have looked into making shadowWorkbook
a global object but it is created on the UI thread and thus cannot subsequently be used from my background operation. One way around this is to save the workbook to disk after each worksheet creation, but there are a lot of creations and this would be expensive.
Is there a way to make shadowWorkbook
global and thread-safe, enabling me to persist changes to my IWorkbook
across thread invocations?
Thanks for your time.
Hmm, I would say that the mutable resource at this.workbookView.ActiveWorkbook
needs to be thread safe in this situation. Your workbook object you say is created on the UI thread, hence you will get contention for it there and in the task threads when you assign workbook = shadowWorkbook
.
Perhaps declare a synchronisation object, eg:
private static Object _objectLock = new Object();
and use like so in your RunScript method (and anywhere else your workbook is modified) to ensure serial access to the resource from different threads:
lock(_objectLock)
{
workbook.AddWorkSheet();
}
In this case you won't need to deep copy your workbook, hence remove that expensive call and all the extra complexity it creates.
If I understand you correctly, what you should do is for each workbook
create a Thread
. On that Thread
, create your shadowWorkbook
, and then run a loop that processes requests for that workbook, using something like BlockingCollection<Action<IWorkbook>>
.
You would use that by writing something like
workbookManager.Run(workbook, w => /* do work and add worksheets to w */);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.