Task.Run vs. Task.Factory.StartNew (Part 2)

2017/01/26

one comment

Task.Run vs. Task.Factory.StartNew (Part 2)

This post continue the previous post about the differences between Task.Run vs. Task.Factory,StartNew.

In the previous part I was speaking about the scheduling aspect, on this part I will spot the async await behavior.

 

Check the following code snippet and think what can go wrong:

Code Snippet
  1. private async Task ExecuteAsync()
  2. {
  3.     var sw = Stopwatch.StartNew();
  4.     await Task.Factory.StartNew(async () =>
  5.         {
  6.             Console.WriteLine("Start section");
  7.             await Task.Delay(1000);
  8.             Console.WriteLine("End section");
  9.         });
  10.     Console.WriteLine(sw.Elapsed);
  11. }

 

What output will it produce?

Will it be something like the following output:


Start section

End section

00:00:01.0000


This is a reasonable guess but it isn’t the actual output.

The output of the code snippet it more like:


Start section

00:00:00.0008

End section


If you will try it with Task.Run:

Code Snippet
  1. private async Task ExecuteAsync()
  2. {
  3.     var sw = Stopwatch.StartNew();
  4.     await Task.Run(async () =>
  5.         {
  6.             Console.WriteLine("Start section");
  7.             await Task.Delay(1000);
  8.             Console.WriteLine("End section");
  9.         });
  10.     Console.WriteLine(sw.Elapsed);
  11. }

You will get something like the first output.

Why is it behave differently? and What happens beneath  the surface?

 

It’s all start with the overloads options of each API.

Task.Run offer Func<Task> and Func<Task<T>> while Task.Factory.StartNew only offer Action and Func<T>.

This it a big difference, because Task.Factory.StartNew don’t offer Func that return Task or Task<T>, therefore await cannot honor.

The following code snippet show the return value of each option.

Code Snippet
  1. Task x = Task.Run(async () => await Task.Delay(1));
  2. Task<Task> y = Task.Factory.StartNew(async () => await Task.Delay(1));

 

This difference, is a lesson for API design. When delegate don’t return a Task, the invocation of it won’t be aware of the ending

of asynchronous operations. in matter of fact asynchronous operation will consider as fire and forget.

Let assume that you have the following API: Action<DIsposableResource>

in this case, if the caller will dispose after the call you want be able to produce async delegate without risking ObjectDisposedException.

See the following code snippet for better understanding:

Code Snippet
  1. public void SafeInvocation<T>(Action<T> action, T resource)
  2.     where T : IDisposable
  3. {
  4.     Console.WriteLine("Invoking");
  5.     using (resource)
  6.     {
  7.         action(resource);
  8.     }
  9.     Console.WriteLine("Invoked");
  10. }
  11.  
  12. private void Exec(FileStream stream)
  13. {
  14.     SafeInvocation(ExecuteAsync, stream);
  15. }
  16.  
  17. private async void ExecuteAsync(FileStream stream)
  18. {
  19.     Console.WriteLine("Writing");
  20.     var buffer = new byte[1000];
  21.     Console.WriteLine("Will it dispose before time?");
  22.     await stream.ReadAsync(buffer, 0, buffer.Length);
  23.     Console.WriteLine("Did it got here or throw ObjectDisposedException?");
  24. }

When SafeExecution got called at line 14, the execution of ExecuteAsync is in great risk of being disposed before time, because

the API didn’t enable indication about the delegate completion time.

If you will get used to design delegate based API with async in mind you can gain better behavior for async execution .

The following code snippet show how this API should have been:

Code Snippet
  1. public async void SafeInvocation<T>(Func<T, Task> action, T resource)
  2.     where T : IDisposable
  3. {
  4.     Console.WriteLine("Invoking");
  5.     using (resource)
  6.     {
  7.         await action(resource);
  8.     }
  9.     Console.WriteLine("Invoked");
  10. }
  11.  
  12. private void Exec(FileStream stream)
  13. {
  14.     SafeInvocation(ExecuteAsync, stream);
  15. }
  16.  
  17. private async Task ExecuteAsync(FileStream stream)
  18. {
  19.     Console.WriteLine("Writing");
  20.     var buffer = new byte[1000];
  21.     Console.WriteLine("Will it dispose before time?");
  22.     await stream.ReadAsync(buffer, 0, buffer.Length);
  23.     Console.WriteLine("Did it got here or throw ObjectDisposedException?");
  24. }

This snippet didn’t change a lot, but it’s much better in terms of enabling parallelism.

The change start at the API level. SafeInvocation (line 1) is getting getting Func<T, Task> rather Action<T>.

This make the all difference.

line 7 await the execution and the definition of ExecuteAsync has changed from void into async Task (line 17).

 

Back to our subject, Task.Run’s delegate honor async delegate by using overloads of Func which return Task or Task<T>.

 

In matter of fact I could do the same with Task.Factory.StartNew but it would require extra step which may be forgotten too many times.

Result of Task<Task> or Task<Task<T>> can be unwrap, i.e. extract the internal task from the result.

Following code snippet will produce the same output as Task.Run do:

Code Snippet
  1. private async Task ExecuteAsync()
  2. {
  3.     var sw = Stopwatch.StartNew();
  4.     await Task.Factory.StartNew(async () =>
  5.         {
  6.             Console.WriteLine("Start section");
  7.             await Task.Delay(1000);
  8.             Console.WriteLine("End section");
  9.         }).Unwrap();
  10.     Console.WriteLine(sw.Elapsed);
  11. }

The trick is the Unwrap at line 10, but this is something which can easily be forgotten.

 

Summary

As I mentioned on part 1 of the series Task.Run is here for ease our life and should be served for

the most common usage of simple parallelism.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. Bnaya Eshet
    Bnaya Eshet2017/01/26 ב 01:00

    just wrote it now 🙂

    Reply