All, I have a situation where I have been asked to multi-thread a large ‘Cost-Crunching’ algorithm. I am relatively experienced with Tasks and would be confident in adopting a pattern like
CancellationTokenSource cancelSource = new CancellationTokenSource();
CancellationToken token = cancelSource.Token;
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task<bool> asyncTask = null;
asyncTask = Task.Factory.StartNew<bool>(() =>
SomeMethodAsync(uiScheduler, token, _dynamic), token);
asyncTask.ContinueWith(task =>
{
// For call back, exception handling etc.
}, uiScheduler);
and then for any operation where I need to provide and UI operation, I would use
Task task = Task.Factory.StartNew(() =>
{
mainForm.progressLeftLabelText = _strProgressLabel;
}, CancellationToken.None,
TaskCreationOptions.None,
uiScheduler);
Where this might be wrapped up in a method.
Now, I realise that I can make all this much less complicated, and leverage the async/await keywords of .NET 4.5. However, I have some questions: if I have a long running method that I launch using
// Start processing asynchroniously.
IProgress<CostEngine.ProgressInfo> progressIndicator =
new Progress<CostEngine.ProgressInfo>();
cancelSource = new CancellationTokenSource();
CancellationToken token = cancelSource.Token;
CostEngine.ScriptProcessor script = new CostEngine.ScriptProcessor(this);
await script.ProcessScriptAsync(doc, progressIndicator, token);
where CostEngine.ProgressInfo is some basic class used to return progress information and the method ProcessScriptAsync is defined as
public async Task ProcessScriptAsync(SSGForm doc, IProgress<ProgressInfo> progressInfo,
CancellationToken token, bool bShowCompleted = true)
{
...
if (!await Task<bool>.Run(() => TheLongRunningProcess(doc)))
return
...
}
I have two questions:
-
To get
ProcessScriptAsyncto return control to the UI almost immediately I await on a newTask<bool>delegate (this seemingly avoids an endless chain ofasync/awaits). Is this the right way to callProcessScriptAsync? [‘Lazy Initialisation’, by wrapping in an outer method?] -
To access the UI from within
TheLongRunningProcess, do I merely pass in the UITaskScheduleruiScheduler; i.e.TheLongRunningProcess(doc, uiScheduler), then use:
Task task = Task.Factory.StartNew(() =>
{
mainForm.progressLeftLabelText = _strProgressLabel;
}, CancellationToken.None,
TaskCreationOptions.None,
uiScheduler);
as before?
Sorry about the length and thanks for your time.
It depends. You’ve shown a lot of code, and yet omitted the one bit that you’re actually asking a question about. First, without knowing what the code is we can’t know if it’s actually going to take a while or not. Next, if you await on a task that’s already completed it will realize this, and not schedule a continuation but instead continue on (this is an optimization since scheduling tasks is time consuming). If the task you await isn’t completed then the continuation will still be executed in the calling
SynchronizationContext, which will again keep the UI thread busy. You can useConfigureAwait(false)to ensure that the continuation runs in the thread pool though. This should handle both issues. Note that by doing this you can no longer access the UI controls in the...sections ofProcessScriptAsync(without doing anything special). Also note that sinceProcessScriptAsyncis now executing in a thread pool thread, you don’t need to useTask.Runto move the method call to a background thread.That’s one option, yes. Although, if you’re updating the UI based on progress, that’s what
IProgressis for. I see you’re using it already, so that is the preferable model for doing this. If this is updating a separate type of progress than the existingIProgressyou are passing (i.e. the status text, rather than the percent complete as an int) then you can pass a second.