使用async/await实现C#大型应用程序的多线程化

7
大家好,我被委托将一个大型 C# 应用程序进行多线程处理。为此,我选择使用 async/await。我很清楚使用 IProgress<T> 报告进度信息到 UI(我们称之为向 UI 推送信息),但我还需要从 UI 中获取数据(在我的情况下是包含数据的 SpreadsheetGear 工作簿)。我需要一些关于这种双向交互的建议...
目前,我通过触发点击事件来启动处理过程,代码结构如下:
CancellationTokenSource cancelSource;
private async void SomeButton_Click(object sender, EventArgs e)
{
    // Set up progress reporting.
    IProgress<CostEngine.ProgressInfo> progressIndicator =
        new Progress<CostEngine.ProgressInfo>();

    // Set up cancellation support, and UI scheduler.
    cancelSource = new CancellationTokenSource();
    CancellationToken token = cancelSource.Token;
    TaskScheduler UIScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    // Run the script processor async.
    CostEngine.ScriptProcessor script = new CostEngine.ScriptProcessor(this);
    await script.ProcessScriptAsync(doc, progressIndicator, token, UIScheduler);

    // Do stuff in continuation...
    ...
}

然后在ProcessScriptAsync中,我有以下内容:

public async Task ProcessScriptAsync(
    SpreadsheetGear.Windows.Forms.WorkbookView workbookView, 
    IProgress<ProgressInfo> progressInfo,
    CancellationToken token, 
    TaskScheduler UIScheduler)
{
    // This is still on the UI thread.
    // Here do some checks on the script workbook on the UI thread.
    try
    {
        workbookView.GetLock();
        // Now perform tests...
    }
    finally { workbookView.ReleaseLock(); }

    // Set the main processor off on a background thread-pool thread using await.
    Task<bool> generateStageTask = null;
    generateStageTask = Task.Factory.StartNew<bool>(() => 
        GenerateStage(workbookView, 
            progressInfo, 
            token, 
            UIScheduler));
    bool bGenerationSuccess = await generateStageTask;

    // Automatic continuation back on UI thread.
    if (!bGenerationSuccess) { // Do stuff... }
    else {
     // Do other stuff
    }
}

目前为止,这看起来还不错。现在我遇到的问题在于方法GenerateStage,该方法现在在后台线程池线程上运行。

private bool GenerateStage(
    SpreadsheetGear.WorkbookView workbookView, 
    IProgress<ProgressInfo> progressInfo, 
    CancellationToken token, 
    TaskScheduler scheduler)
{
    ...
    // Get the required data using the relevant synchronisation context.
    SpreadsheetGear.IWorksheet worksheet = null;
    SpreadsheetGear.IRange range = null;
    Task task = Task.Factory.StartNew(() =>
    {
        worksheet = workbookView.ActiveWorksheet;
        range = worksheet.UsedRange;
    }, CancellationToken.None,
       TaskCreationOptions.None,
       scheduler);
    try
    {
        task.Wait();
    }
    finally
    {
        task.Dispose();
    }

    // Now perform operations with 'worksheet'/'range' on the thread-pool thread...
}

在这种方法中,我需要多次从用户界面(UI)拉取数据并将数据写入UI。对于写入,我可以明确使用'progressInfo',但如何处理从UI提取信息。在这里,我使用了UI线程同步上下文,但这将会被多次执行。是否有更好的方法来执行这些操作/当前方法是否存在任何缺陷? 注意:显然我需要把Task.Factory.StartNew(...) 的代码封装成可重用的方法,以上内容仅为简洁起见而显示。

1
所以,WorkbookView 必须从 UI 线程访问,但 IWorksheetIRange 可以从其他线程使用?你为什么不只是将这两个对象传递给 GenerateStage() 呢? - svick
1
workbookViewAsync = workbookView 不会创建副本,它只是将引用复制到另一个变量。但新变量仍将指向旧对象。 - svick
第一篇帖子提出了一个合理的建议,但是要在worksheetIRange上执行操作,您必须在工作簿集或WorkbookView上调用GetLock(),这又意味着访问在UI线程上创建的WorkbookView - MoonKnight
你的第二点也很好!这是一个考虑不周的建议,我会将其删除... - MoonKnight
@svick 我开发了一个测试应用程序 - 小巧但可以执行我目前关心的操作。我困惑的另一件事是,在测试应用程序中,我可以在GenerateStage中访问workbookView _WITHOUT_更改同步上下文 - 我不知道为什么会发生这种情况,如果我更新其他控件,我会得到通常的InvalidOperationException??非常奇怪... - MoonKnight
2个回答

2

如果你不断地在UI和线程池线程之间来回切换,那么你的代码会有些凌乱。

你基本上有两个选择:把“正常”的上下文设置为线程池线程,并将部分内容安排到UI线程(就像现在这样),或者将“正常”的上下文设置为UI线程,并将部分内容安排到线程池线程中。

我通常更喜欢后一种方法,因为你可以使用更简单的Task.Run,而不是在特定TaskScheduler上使用Task.Factory.StartNew。但无论哪种方式,代码都会有些凌乱。


我并不完全相信我所做的是最好的方法,但我真的看不到其他的方法。这段代码又老又复杂 - 重新设计以支持多线程似乎并不必要(因为代码已经很好地完成了它的工作),而且在这种情况下,我不知道它如何帮助我。我主要关注我正在启动的Task数量,以从UI中提取数据。显然,生成这些线程将具有不可避免的开销 - 我还不知道什么样的开销...感谢您的时间。 - MoonKnight

0

我没有使用过SpreadsheetGear工作簿,但我认为它有一个事件机制,您可以使用它来将相关数据存储在自定义对象中,您可以从UISynchronizationContext的外部访问该对象,以此避免与工作簿UI控件强耦合。这将使您避免阻塞UI线程。

 Task task = Task.Factory.StartNew(() =>
{
    worksheet = workbookView.ActiveWorksheet;
    range = worksheet.UsedRange;
}, CancellationToken.None,
   TaskCreationOptions.None,
   scheduler);
try
{
    task.Wait();
}
finally
{
    task.Dispose();
}

来自GenerateStage方法的一部分。

但是,我的建议基于对SpreadsheetGear工作簿控件的一些假设,这些假设可能是不正确的。


1
您可以查看文档来确认或驳斥您的假设。 - svick

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