0

I'm new to async/await c# model and trying to understand if these two options are essentially the same thing:

public Task LongRunningMethod()
{
    return Task.Run(async () =>
    {
        await DoStuff();
    });
}

//then call it
await LongRunningMethod();

and this

public async Task LongRunningMethod()
{
    await DoStuff();
}

//then call it
await LongRunningMethod();

I'm thinking the 1st way will use up an extra thread from the pool... And it also wraps a Task into an extra Task. Or am I wrong?

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149

3 Answers3

7

Task.Run will queue its delegate to the thread pool. This causes two important things:

  1. Any code in DoStuff before the first asynchronous await will run on a thread pool thread, instead of on the calling thread.
  2. The code in DoStuff will run in a thread pool context, instead of using whatever context was current from the calling thread.

Most of the time, doing asynchronous work in Task.Run is a mistake. But it is sometimes useful, say if DoStuff does some heavy computational work before it acts asynchronously, then Task.Run could be used to move that work off of a UI thread.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
2

They are very similar, but there is a chance that DoStuff could complete synchronously; if that happens, your second (await only) method would block for however long that takes, where-as the first (Task.Run) would always return incomplete to the caller (unwinding their stack and scheduling a continuation), and have the blocking work run on the pool thread.

Both options can be desirable, in different scenarios!

However, there is a third option, which expresses exactly the intent of "run the rest somewhere else" as long as you don't have a SyncContext:

public async Task LongRunningMethod()
{
    await Task.Yield();
    await DoStuff(); // possibly with .ConfigureAwait(false)
}

If there is no SyncContext, this is functionally similar to the Task.Run version (meaning: it will always return to the caller as incomplete, and run the DoStuff starting on the pool), just: without the actual Task.Run bits. However, if there is a SyncContext: it will go back onto the same context, which is ... awkward, and unfortunately there is no ConfigureAwait(false) for YieldAwaitable, so in that case you'd have to use Task.Run or similar.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • You should call yourself the god of `System.Threading.Tasks.Task`. – Tanveer Badar Jun 17 '20 at 16:16
  • 4
    @TanveerBadar yield before me! – Marc Gravell Jun 17 '20 at 16:20
  • If LongRunningMethod is called on the gui thread then DoStuff will also run on the gui thread, not in the thread pool. You'll need a ConfigureAwait to get it into the pool. – Sean Jun 18 '20 at 08:06
  • 1
    @Sean yes, you're right - if there's a sync context, it will go *back* to the sync context; I'll reflect that (I've gotten so used to living in a world without sync-context; I don't miss them!) – Marc Gravell Jun 18 '20 at 08:41
  • Why prefer `await Task.Yield` over `await Task.Run(async () =>`? Is it more efficient? Or just because it's more expressive? (assuming it is) – Theodor Zoulias Jun 18 '20 at 09:07
  • @TheodorZoulias mostly about expressing intent, although I believe it does shave a few pieces - but not enough to matter in any realistic scenarios (if you're hammering `Yield` that much, you're probably not batching enough) – Marc Gravell Jun 18 '20 at 09:16
1

The method name conveys the reason why it is done like that, "LongRunning". You can pass a creation flag LongRunning to Task.Run() (which this sample doesn't) and as a pure implementation thread, runtime will allocate a separate thread for that.

Even your second example isn't completely correct, depending on circumstances. If this is a library, then it really should be await DoStuff().ConfigureAwait(false);.

Tanveer Badar
  • 5,438
  • 2
  • 27
  • 32