1

I have this code:

var task = Task.Factory.StartNew(() => service.StartAsync(ct), ct);

but I'm wondering if it should instead be this:

var task = Task.Factory.StartNew(async () => await service.StartAsync(ct), ct).Unwrap();

Is the first one correct to start my async service? Or is the second one better?

JSteward
  • 6,833
  • 2
  • 21
  • 30
Sean B
  • 11,189
  • 3
  • 27
  • 40
  • 2
    Rather than creating a task, wrapping it in another task, unwrapping it in another task, wrapping it in a task again, and unwrapping it in a task again, why not just *not wrap the task in another task in the first place*? Just write `var task = service.StartAsync(ct);`, given that the operation is already asynchronous. – Servy Aug 30 '18 at 21:50
  • Task task = service.StartAsync(ct); blocks until the service completes, which is not the desired behavior. – Sean B Aug 31 '18 at 13:00
  • No, it doesn't. It's asynchronous. That's why it has `Async` in the name and returns a `Task`. If it's not asynchronous, it shouldn't return a `Task` and it shouldn't have `Async` in the name, or it has a bug that you need to fix to make it asynchronous. – Servy Aug 31 '18 at 13:21
  • 1
    linked already answered question doesnt mention unwrap at all which does change some things. – user1496062 Dec 01 '22 at 23:44

2 Answers2

1

Consider the type of task returned, the first one yields Task<Task<int>> while the second yields Task<int>. So really the first one is a Task representing starting of the inner task, while the second, unwrapped, represents the Task returned by the inner method representing the service starting. Finally you can also Unwrap the first and get the same effect without the async/await which is unnecessary here. None of this really covers what the need for StartNew is at all in this case just reviews the return types your looking at.

Consider the following code:

public class AsyncTesting
{
    public void StartServiceTest()
    {
        Task<Task<int>> tsk1 = Task.Factory.StartNew(() => StartAsync());
        Task<int> tsk2 = Task.Factory.StartNew(() => StartAsync()).Unwrap();
        Task<int> tsk3 = Task.Factory.StartNew(async () => await StartAsync()).Unwrap();
    }

    public Task<int> StartAsync() => Task.Delay(2500).ContinueWith(tsk => 1);
}

The method that does not Unwrap returns a Task that represents starting the internal Task not the work it does.

JSteward
  • 6,833
  • 2
  • 21
  • 30
0

As JSteward explain in their answer, the first line of code is wrong. It doesn't do what you expect it to do:

Task<Task> task = Task.Factory.StartNew(() => service.StartAsync(ct), ct); // Buggy

The second line has the correct behavior, but not because of the async/await. The async/await can be safely elided. What makes it correct is the Unwrap. It is still problematic though, because it violates the guideline CA2008 about not creating tasks without passing a TaskScheduler.

The best way to solve your problem is to use the Task.Run method:

Task task = Task.Run(() => service.StartAsync(ct), ct); // Correct

You can read about the differences between Task.Run and Task.Factory.StartNew in this article by Stephen Toub.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104