Use and understanding of async/await in .NET 4.5 +

Posted on

Problem

I am just about to embark upon a massive job of multi-threading a cost-engine. I could use TPL of which I am very familiar, but would like to leverage the async/await keywords of .NET 4.5 to make life that little bit simpler.

Is my understanding of what is going on correct? How can I improve this design?

CancellationTokenSource cancelSource;

// Mark the event handler with async so you can use await in it.
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
    cancelSource = new CancellationTokenSource();
    CancellationToken token = cancelSource.Token;
    TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    IProgress<ProgressInfo> progressInfo = new Progress<ProgressInfo>(ReportProgress);
    await ProcessScript(progressInfo, token, uiScheduler);

    // await sets up the following as an Contnuation to 
    // continue once returned from await.
    this.resultsTextBox.AppendText("nReturn to caller...nn");
}

// Class to report progress...
private void ReportProgress(ProgressInfo progressInfo)
{
    this.progressBar.Value = progressInfo.PrecentComplete;
    this.progressLabel.Content = progressInfo.Message;
}

private async Task ProcessScript(IProgress<ProgressInfo> progressInfo,
                                 CancellationToken token,
                                 TaskScheduler uiScheduler)
{
    ProgressInfo pi = new ProgressInfo();
    pi.PrecentComplete = 0;
    pi.Message = "In script processor...";
    progressInfo.Report(pi);

    Thread.Sleep(5000); // This is UI Blocking...
    string str = this.resultsTextBox.Text;
    str = "We have added this => going into await...";
    this.resultsTextBox.AppendText(str);
    Task task = Task.Factory.StartNew(() => LongRunning(uiScheduler));

    pi.Message = "Task set";
    pi.PrecentComplete = 50;
    progressInfo.Report(pi);

    // awaits the long runniing task - non-UI blocking.
    await task;

    // The await above sets this up as a continuation on the UI thread.
    pi.Message = "Task awaited";
    pi.PrecentComplete = 95;
    progressInfo.Report(pi);

    this.resultsTextBox.AppendText("Where are we now??");
    return;
}

private bool LongRunning(TaskScheduler uiScheduler)
{
    // Allow access to the UI from this background thread from 
    // the ThreadPool.
    Task.Factory.StartNew(() =>
        {
            this.resultsTextBox.AppendText("nnNow in 'LongRunning'!! Waiting 5s simulating HARD WORK!!");
        }, CancellationToken.None,
           TaskCreationOptions.None,
           uiScheduler);

    // Simulate hard work.
    Thread.Sleep(7000);
    Task.Factory.StartNew(() =>
    {
        this.resultsTextBox.AppendText("nnnHARD WORK COMPLETE!!");
    }, CancellationToken.None,
       TaskCreationOptions.None,
       uiScheduler);
    return true;
}

Solution

Yes, your understanding is correct. Note that you’re not using CancellationToken provided to ProcessScript. If your operations are cancellable and/or you’re using other asynchronous API that accepts CancellationTokens it’s highly recommended to pass it through the code, otherwise just don’t create the CancellationTokenSource.

Following your comments:

Thread.Sleep(5000); // This is UI Blocking...

This is correct since async methods return control to the caller only when something is awaited.

// awaits the long runniing task - non-UI blocking.
await task;

Here we release the UI thread and running a (computing) task in parallel. The reason I’ve added “computing” is that you’re manually spanning a new task using Task.Factory. In case of I/O-related task returned by .NET framework it won’t actually represent a new thread as it can wait for external data on the same thread.

// The await above sets this up as a continuation on the UI thread.

Correct. See good article Await, SynchronizationContext, and Console Apps that describes in details the behavior of await.

// Allow access to the UI from this background thread from 
// the ThreadPool.
Task.Factory.StartNew(() =>
    {
        this.resultsTextBox.AppendText("nnNow in 'LongRunning'!! Waiting 5s simulating HARD WORK!!");
    }, CancellationToken.None,
       TaskCreationOptions.None,
       uiScheduler);

You’re passing TaskScheduler corresponding to UI thread so naturally the code will be safe to work with UI. I would recommend using IProgress<T> to update/inform UI about changes in long-running task though, since you may want to separate computation logic from UI-related code.

Leave a Reply

Your email address will not be published. Required fields are marked *