【问题标题】:Multi-Threading a Large C# Application Using async/await使用 async/await 对大型 C# 应用程序进行多线程处理
【发布时间】:2013-01-08 02:06:52
【问题描述】:

所有,我被赋予了对大型 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(...) 代码包装成一个可重用的方法,为了简洁起见,上面明确显示。

【问题讨论】:

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

标签: c# multithreading task-parallel-library .net-4.5 async-await


【解决方案1】:

我没有使用 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 工作簿控件的一些假设,这可能是不正确的。

【讨论】:

【解决方案2】:

如果您经常在 UI 和线程池线程之间来回切换,您的代码会有点混乱。

您基本上有两个选择:让您的“正常”上下文是线程池线程,其中部分计划到 UI 线程(就像您现在拥有的那样),或者让您的“正常”上下文是部分计划到的 UI 线程一个线程池线程。

我通常更喜欢后者,因为您可以在特定的TaskScheduler 上使用更简单的Task.Run 而不是Task.Factory.StartNew。但不管怎样,代码都会有点乱。

【讨论】:

  • 我也不完全相信我正在做的是最好的方法,但我真的看不到另一个。代码既旧又复杂——为了支持多线程而重新设计,不仅看起来没有必要(因为代码写得很好),但在这种情况下,我看不出它对我有什么帮助。我主要关心的是我要从 UI 中提取数据的 Tasks 的数量。显然,生成这些线程将产生不可避免的开销——我还不知道什么样的开销......感谢您的时间。
猜你喜欢
  • 1970-01-01
  • 2013-05-16
  • 1970-01-01
  • 2019-02-27
  • 2020-06-26
  • 1970-01-01
  • 1970-01-01
  • 2016-09-01
  • 1970-01-01
相关资源
最近更新 更多