简体   繁体   中英

How to Make a Global Object Thread-Safe

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM