使用async / await多线程处理大型C#应用程序

时间:2013-01-24 10:38:48

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

所有,我已经获得了多线程大型C#应用程序的工作。为此,我选择使用async / await。我很清楚使用IProgress<T>向UI报告进度(让我们称之为'推送'信息到UI),但我还需要从中“拉”数据UI(在我的例子中是一个包含数据的SpreadsheetGear工作簿)。正是这种双向互动,我想要一些建议......

目前我触发click事件以开始处理,代码具有以下结构:

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(...)代码包装成一个可重用的方法,上面的内容是明确显示的。

2 个答案:

答案 0 :(得分:2)

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

你基本上有两个选择:让你的“普通”上下文成为线程池线程,其中部分调度到UI线程(就像现在一样),或者让你的“普通”上下文成为UI线程,其中部分被安排到线程池线程。

我通常更喜欢后者,因为您可以在特定Task.Run上使用更简单的Task.Factory.StartNew代替TaskScheduler。但无论哪种方式,代码都会有点混乱。

答案 1 :(得分: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工作簿控件的一些假设,这可能是不正确的。