13

As I understand it, Task.Yield at the beginning of a method will force the caller to continue if it is not awaiting the method. Meanwhile Task.Run and ConfigureAwait(false) both run a Task on a new thread pool thread, which again will force the caller to continue if it is not awaiting the method.

I can't understand the difference between Task.Yield and running a new thread pool thread, since right after it returns to the caller, it will continue executing the rest of the method, which is essentially the same thing.

This post suggests that Yield and Task.Factory.StartNew (which is really just the old version of Task.Run) can be used interchangeably, which seems confusing to me.

Community
  • 1
  • 1
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 1
    Task.Run = Task.Factory.StartNew – hungndv Jul 04 '15 at 15:27
  • 1
    @hungndv `Task.Run` is `Task.Factory.StartNew` but on the Default Task Scheduler (The thread pool task scheduler). `Task.Factory.StartNew`without specifying a task scheduler will pick the current task scheduler, which may not be the default one. It could be the UI task scheduler for instance. `Task.Run` always executes in the threadpool. So, `Task.Run` is a special case of `Task.Factory.StartNew`, and **not** the new version of it – Thanasis Ioannidis Mar 22 '17 at 11:01

2 Answers2

12

Task.Yield is not a replacement for Task.Run and it has nothing to do with Task.ConfigureAwait.

  • Task.Yield - Generates an awaitable that completes just after it is checked for completion.
  • ConfigureAwait(false) - Generates an awaitable from a task that disregards the captured SynchronizationContext.
  • Task.Run - Executes a delegate on a ThreadPool thread.

Task.Yield is different than ConfigureAwait in that it is an awaitable all by itself, and not a configurable wrapper of another awaitable (i.e. the Task). Another difference is that Task.Yield does continue on the captured context.

Task.Run is different then both as it just takes a delegate and runs it on the ThreadPool, you can use it with ConfigureAwait(false) or without.

Task.Yield should be used to force an asynchronous point, not as a replacement to Task.Run. When await is reached in an async method it checks whether the task (or other awaitable) already completed and if so it continues on synchronously. Task.Yield prevents that from happening so it's useful for testing.

Another usage is in UI methods, where you don't want to be hogging the single UI thread, you insert an asynchronous point and the rest is scheduled to be executed in a later time.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 9
    `Task.Yield` doesn't work to avoid UI throttling, because the UI message queues are prioritized (and continuations always take precedence over `WM_PAINT`). – Stephen Cleary Jul 05 '15 at 02:47
  • Also you never want to paint as fast as you can. That burns one core for nothing. – usr Jul 05 '15 at 09:38
  • @StephenCleary : who posts the continuations in the UI message queue ? This can only happen if some BeginInvoke is called at some point right – v.oddou Sep 01 '15 at 03:19
  • 2
    @v.oddou: Follow-up questions should be their own question (SO is a Q&A site, not a forum). Short answer: `await` will post the method continuation to the current `SynchronizationContext` instance, which uses `BeginInvoke` (or equivalent) under the covers. – Stephen Cleary Sep 01 '15 at 09:00
7

Task.Yield continues on the current synchronization context or on the current TaskScheduler if one is present. Task.Run does not do that. It always uses the thread-pool.

For example Task.Yield would stay on the UI thread.

Avoid Task.Yield. It's semantics are less clear. The linked answer is a code smell.

usr
  • 168,620
  • 35
  • 240
  • 369
  • A good point about the use of `TaskScheduler.Current` by `Task.Yield` - another questionable design decision of the TPL team. And this one cannot be justified by .NET 4.0 compatibility reasons, as there's no `Task.Yield` in .NET 4.0. – noseratio Jul 05 '15 at 04:38
  • @Noseratio How is it questionable? `await` continues on the captured context unless explicitly told otherwise which is a safe default. Why should it be different with `Task.Yield`. – i3arnon Jul 05 '15 at 10:09
  • @i3arnon, I'm not questioning the `Task.Yield` behavior when synchronization context is present. But in the absence of one, I'm questioning the use of `TaskScheduler.Current` vs `TaskScheduler.Default`. I strongly believe the latter should have been used, as it is with `Task.Run`. – noseratio Jul 05 '15 at 10:19
  • I really wouldn't call the default choice of resuming on some configurable context a safe choice! It is convenience for UI apps but besides that it is very dangerous. A hidden dependency on mutable global state that can inject arbitrary threading behavior. Very unsafe. – usr Jul 05 '15 at 10:20
  • @Noseratio I wasn't talking about the SC as well. `await` resumes on the current TS, there's no reason it should be any different with `Task.Yield`. `Task.Run` has nothing to do with `Task.Yield`, it explicitly executes code on a different TS. – i3arnon Jul 05 '15 at 10:24
  • @usr If it's convenient for UI apps (and asp.net apps as well) then that's the safe choice. It's not without its issues, but there would be much more if they didn't take these dependencies. – i3arnon Jul 05 '15 at 10:27
  • @i3arnon, in the absence of SC, the use of `TaskScheduler.Current` for `await` was IMO a bad decision, too. A very non-intuitive and subtle behavior. Toub gives a good example of how it all can go wrong [here](http://blogs.msdn.com/b/pfxteam/archive/2012/09/22/new-taskcreationoptions-and-taskcontinuationoptions-in-net-4-5.aspx). – noseratio Jul 05 '15 at 10:42