所有,我已经获得了多线程大型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(...)
代码包装成一个可重用的方法,上面的内容是明确显示的。
答案 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工作簿控件的一些假设,这可能是不正确的。