如何使全局对象线程安全

3

大家好,我最近将一个大型应用程序多线程化后,遇到了并发问题。问题出在可以运行批处理作业的脚本处理器中。

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.
        }
    }
...
}   

当考虑到RunScript方法时,我的问题出现了。传递给RunScript的对象不是线程安全的,并且是在UI线程上创建的。因此,在RunScript方法内部我必须创建该对象的“深拷贝”...

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();
    }
}

我的问题出现在(##)标记的行上。每次执行RunScript时,都会从磁盘创建一个新的shadowWorkbook。问题是,在shadowWorkbook中创建了一些工作表,随后将这些工作表复制回workbook进行处理。但是,每次执行RunScript时,我都会从磁盘获取workbook,其中没有生成在最后一个循环中的新工作表。
我尝试将shadowWorkbook设为全局对象,但它是在UI线程上创建的,因此不能从后台操作中使用。解决这个问题的方法之一是在每个工作表创建后将工作簿保存到磁盘,但这样做会很耗费资源。 是否有一种方法可以使shadowWorkbook成为全局变量并且线程安全,以便能够跨线程调用保留对我的IWorkbook进行更改? 感谢你的时间。

也许 Volatile 是你正在寻找的。 - noobob
2
@noobob 或许不是。在这种特定情况下,“volatile”会有什么作用?应该应用于哪个字段? - C.Evenhuis
1
你是说你想同时从几个线程修改文件吗?恐怕没有简单的方法可以做到这一点。 - svick
不,我认识到这是不可能的。我遇到的问题与每次后台线程需要执行工作时都必须创建一个新的“workbook”对象有关。在批处理过程中,“RunScript”方法被调用多次,每次对“shadowWorkbook”进行操作 - 问题是我无法在运行脚本的调用之间保留对“shadowWorkbook”的更改,而不执行“硬”保存(即写入磁盘),这是昂贵的。我想知道如何使“shadowWorkbook”全局,而不会引起跨线程问题... - MoonKnight
2个回答

1
嗯,我认为在这种情况下,this.workbookView.ActiveWorkbook 中的可变资源需要是线程安全的。你所说的工作簿对象是在UI线程上创建的,因此当你分配 workbook = shadowWorkbook 时,在UI线程和任务线程中都会争用它。
也许声明一个同步对象,例如:
private static Object _objectLock = new Object();

在您的RunScript方法中(以及任何其他修改工作簿的地方),请使用以下方式使用,以确保不同线程对资源进行串行访问:

lock(_objectLock)
{
    workbook.AddWorkSheet();
}

在这种情况下,您不需要深度复制工作簿,因此请删除该昂贵的调用以及它所创建的所有额外复杂性。

+1 谢谢您的想法,但这对我不起作用。当您说“在这种情况下,此.workbookView.ActiveWorkbook的可变资源需要是线程安全的”时,那将是很好的,但它并不是,并且这是第三方库。分配workbook(UI线程拥有)对象并不是问题,希望能够重复使用shadowWorkbook而不需要线程上下文切换。 - MoonKnight

1
如果我理解正确,你需要做的就是为每个工作簿创建一个线程。在该线程上,创建你的 shadowWorkbook,然后运行一个循环来处理该工作簿的请求,使用类似于 BlockingCollection<Action<IWorkbook>> 的东西。你可以像下面这样编写代码来使用它。
workbookManager.Run(workbook, w => /* do work and add worksheets to w */);

感谢您的想法。对 IWorkbook 对象执行的操作必须是顺序的;而且这种方法似乎也无法解决我的问题,因为在每个线程上,我仍然需要在启动每个线程时创建一个 IWorkbook 对象,并以线程安全的方式执行此操作需要使用 shadowWorkbook = SpreadsheetGear.Factory.GetWorkbook(workbook.FullName);,这将在相关线程上打开一个实例 - 我希望在执行之间保持对 shadowWorkbook 的使用。目前,我已经添加了保存来解决这个问题... - MoonKnight
考虑到这一点,使用我目前的设计似乎不可能实现... - MoonKnight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接