Share via


Threads Versus Tasks

This article will show the difference in behavior of using the ThreadPool or Tasks in you Windows Store apps and the things you have to remember when using them.

First look at this example:

private async Task DoOperation(string s)

{

            Debug.WriteLine("{0}3", s);

            await Task.Delay(1);

            Debug.WriteLine("{0}4", s);

}

private async void Button_Click_1(object sender, RoutedEventArgs e)

{

Debug.WriteLine("A1");

      await ThreadPool.RunAsync((operation) => { DoOperation("A").Wait(); }, WorkItemPriority.Normal);

      Debug.WriteLine("A9");

}

The result will be A1, A3, A4, A9. This will be most people are expecting. But in this case we locking the thread that is doing a delay of 1 millisecond. We can write it more efficient by not using Wait() method but awaiting it like this:

private async void Button_Click_2(object sender, RoutedEventArgs e)

{

     Debug.WriteLine("B1");

     await ThreadPool.RunAsync(async (operation) => await DoOperation("B"), WorkItemPriority.Normal);

     Debug.WriteLine("B9");

}

Expecting the same result? No! it will return B1, B3, B9, B4. The thread will finish as soon the Task.Delay is called because the callback that an await is causing, and this is where tasks are getting really powerful, because when rewriting this code to use tasks like this

private async void Button_Click_3(object sender, RoutedEventArgs e)

{

   Debug.WriteLine("T1");

   await Task.Run(async () => await DoOperation("T"));

   Debug.WriteLine("T9");

}

The result here will be: T1, T3, T4, T9. The Task that is created by the Task.Run in this method will wait with finishing till the Task that is returned by DoOperation is done. With threads you have to manage this yourself.

Now often the Dispatcher is used to invoke code from a non-ui thread to the UI and if using async code in that dispatcher you get the same behavior as we saw with the second example.

Debug.WriteLine("step 1");

await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>

{

                Debug.WriteLine("step 2");

                await Task.Delay(1000);

                Debug.WriteLine("step 3");

});

Debug.WriteLine("step 4");

The result will be 1,2 4, 3 and in most cases you want to have 1,2,3,4.

When using this little extension method you can fix that behavior

public static class DispatcherExtensions

{

        public static Task RunTaskAsync(this CoreDispatcher dispather, Windows.UI.Core.CoreDispatcherPriority priority, Func<Task> task)

        {

            TaskCompletionSource<object> tsc = new TaskCompletionSource<object>();

            var t = dispather.RunAsync(priority, async () =>

            {

                try

                {

                    await task();

                    tsc.SetResult(null);

                }

                catch (Exception e)

                {

                    tsc.SetException(e);

                }

            });

            return tsc.Task;

        }

 }

The usage is the same as RunIdle

Debug.WriteLine("step 1");

await Dispatcher.RunTaskAsync(CoreDispatcherPriority.Normal, async () =>

{

                Debug.WriteLine("step 2");

                await Task.Delay(1000);

 

                Debug.WriteLine("step 3");

});

Debug.WriteLine("step 4");

Only now it will give the result as wanted!