56

I have this construct in my main(), which creates

var tasks = new List<Task>();

var t = Task.Factory.StartNew(
    async () =>
    {
        Foo.Fim();
        await Foo.DoBar();
    });

//DoBar not completed
t.Wait();
//Foo.Fim() done, Foo.DoBar should be but isn't

However, when I .Wait for t, it won't wait for the call to DoBar() to complete. How do I get it to actually wait?

i3arnon
  • 113,022
  • 33
  • 324
  • 344
Nathan Cooper
  • 6,262
  • 4
  • 36
  • 75
  • 3
    Please show a short but *complete* program demonstrating the problem. – Jon Skeet Jul 16 '14 at 09:36
  • 1
    [Stephen Taub wrote a great article](http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx) about how to start tasks. There's actually an example in that article which looks *exactly* like your example (i.e. `Task.StartNew(async() => ...)`) and which explains what's going on – default Jul 16 '14 at 09:50

3 Answers3

114

It's discouraged to use Task.Factory.StartNew with async-await, you should be using Task.Run instead:

var t = Task.Run(
    async () =>
    {
        Foo.Fim();
        await Foo.DoBar();
    });

The Task.Factory.StartNew api was built before the Task-based Asynchronous Pattern (TAP) and async-await. It will return Task<Task> because you are starting a task with a lambda expression which happens to be async and so returns a task. Unwrap will extract the inner task, but Task.Run will implicitly do that for you.


For a deeper comparison, there's always a relevant Stephen Toub article: Task.Run vs Task.Factory.StartNew

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 1
    Interesting, that is a sneaky difference for `Task.Run`. This additionally answers my confusion about why Task.Run is different with the async/awaits. Thanks. – Nathan Cooper Jul 16 '14 at 09:50
  • @NathanCooper that's what happens in a library with incremental additions and no breaking changes. – i3arnon Jul 16 '14 at 09:51
  • 1
    Yeah, my understanding was that it was the same with different defaults. Being equivalent to `"Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);"` as the article says. This clearly needs further reading when your using async/await rather than simple actions. – Nathan Cooper Jul 16 '14 at 09:56
  • 1
    I was losing my mind today trying to find out why Factory.Start was returning Task – tylerjgarland May 20 '19 at 18:15
  • 2
    Was about to ask about how to pass the LongRunning parameter using the new factory, and then found this excellent blog post you wrote: http://blog.i3arnon.com/2015/07/02/task-run-long-running/ Your answer combined with that post really helped me out! – istrupin Oct 24 '19 at 15:50
8

It seems like I get desired functionality by Unwrap()ing the task. I'm not quite sure I get the reasoning behind this, but I suppose it works.

var t = Task.Factory.StartNew(
            async () =>
                {
                        Foo.Fim();
                        await Foo.DoBar();
                }).Unwrap();

edit: I've looked for ddescription of Unwrap(): Creates a proxy Task that represents the asynchronous operation of a Task<Task<T>> I thought this was traditionally what the task did, but if I need to call unwrap I suppose that's fine.

Nathan Cooper
  • 6,262
  • 4
  • 36
  • 75
  • Yep, the other answer is pretty comprehensive. `Task.Run()` seems the way to go. – Nathan Cooper Jul 16 '14 at 10:02
  • Are you sure you need `Foo.Dim()` inside the task, in the first place? I.e., maybe you can simply do this: `Foo.Fimm(); Foo.DoBar().Wait();` - without creating a new `Task` via `Run` or `StartNew`. – noseratio Jul 16 '14 at 10:13
  • 1
    @Noseratio Fair point. I've not given the context to show why it is needed (it is), but its always good to check whether we've over-engineered things. – Nathan Cooper Jul 16 '14 at 10:29
  • `Unwrap()` just ends up throwing a `Start may not be called on a promise-style task.` exception :/ – Douglas Gaskell Jul 31 '17 at 01:25
0

I faced similar issue recently and figured out that all you need to do is have DoBar() return some value and use .Result instead of wait.

var g = Task.Run(() => func(arg));

var val = g.Result;

This will wait for func to return its output and assign it to val.

Draken
  • 3,134
  • 13
  • 34
  • 54
Siddharth Shakya
  • 122
  • 1
  • 10
  • -1 because the OP wasn't looking to return a value, the OP's question was about async/await where this is purely synchronous, and this seems to be returning a throwaway value for the sake of calling Result instead of Wait(). If you wanted to do the above in an async manner, you'd use "var val = await g;" to await the result of the task. If you don't care about the result, then you can simply .Wait() on the task--no need to return a throwaway value from your function and wait using Result. – Josh Aug 29 '18 at 23:29
  • 1
    his question was "How do I get it to actually wait ? ". where has he said anything about return value ? – Siddharth Shakya Aug 30 '18 at 09:56