ParallelExtensionsExtras Tour – #15 – Specialized Task Waiting

(The full set of ParallelExtensionsExtras Tour posts is available here.)

The Task Parallel Library provides the Task.Wait method, which synchronously waits for the target Task to complete.  If the Task completed successfully, the method simply returns.  If the Task completed due to an unhandled exception or cancellation, Wait throws an appropriate exception to connote that you can’t just blindly continue expecting the results or side-effects of the Task to have taken place.

While this is a very useful form of waiting, there are others that can be beneficial in certain situations, and ParallelExtensionsExtras includes a few different forms in the TaskExtrasExtensions.cs file.

WaitWithPumping

In a Windows Presentation Foundation application, especially when doing unit testing, you sometimes need to wait for a task on the UI thread.  However, in doing so you don’t want to block the UI thread, and instead you want to continue the WPF message loop, maintaining a responsive application.  For this purpose, ParallelExtensionsExtras includes the WaitWithPumping extension method for Task.

public static void WaitWithPumping(this Task task)

{

    if (task == null) throw new ArgumentNullException(“task”);

    var nestedFrame = new DispatcherFrame();

    task.ContinueWith(_ => nestedFrame.Continue = false);

    Dispatcher.PushFrame(nestedFrame);

    task.Wait();

}

 

WaitWithPumping enters a message loop that will only exit when the task completes, which WPF knows about through a continuation applied to the task.  Once the message loop has exited, we Wait on the task simply to propagate exceptions in the case where the task did not complete successfully.

With that in place, you could write WPF code like the following:

private void button1_Click(object sender, RoutedEventArgs e)

{

    var t = Task.Factory.StartNew(() => Thread.Sleep(5000));

 

    t.WaitWithPumping(); // UI remains responsive during call

 

    MessageBox.Show(t.Status.ToString()); // will show “RanToCompletion”

}

 

Even thought Task t won’t complete for 5 seconds, and even though WaitWithPumping will block, the UI will still remain responsive.  Then, because we waited for the Task to complete before exiting the message loop, the MessageBox will show “RanToCompletion”.

WaitForCompletionStatus

Sometimes you want to wait for a Task, but you don’t want the Wait operation to throw an exception, even if the target task completed in the Faulted or Canceled states.  To achieve this, ParallelExtensionsExtras provides the WaitForCompletionStatus extension method on Task:

public static TaskStatus WaitForCompletionStatus(this Task task)

{

    if (task == null) throw new ArgumentNullException(“task”);

    ((IAsyncResult)task).AsyncWaitHandle.WaitOne();

    return task.Status;

}

 

This method relies on the fact that Task implements IAsyncResult, and thus implements the AsyncWaitHandle property.  AsyncWaitHandle returns a WaitHandle that will be set when the task completes, and waiting on this wait handle will not throw exceptions in the same manner as does waiting on the task directly.

WaitForCompletionStatus returns the final TaskStatus of the task that was waited on.  This makes it easy to write code that switches on a task’s completion state in order to do appropriate follow-up processing, e.g.

switch(task.WaitForCompletionStatus)

{

    case TaskStatus.RanToCompletion:

        Console.WriteLine(“Woo hoo!”);

        break;

    case TaskStatus.Faulted:

        Console.WriteLine(“Uh oh: “ + task.Exception.Message);

        break;

    case TaskStatus.Canceled:

        Console.WriteLine(“Oh well.”);

        break;

}