36

I've been reading about Task.Yield , And as a Javascript developer I can tell that's it's job is exactly the same as setTimeout(function (){...},0); in terms of letting the main single thread deal with other stuff aka :

"don't take all the power , release from time time - so others would have some too..."

In js it's working particular in long loops. ( don't make the browser freeze...)

But I saw this example here :

public static async Task < int > FindSeriesSum(int i1)
{
    int sum = 0;
    for (int i = 0; i < i1; i++)
    {
        sum += i;
        if (i % 1000 == 0) ( after a bulk , release power to main thread)
            await Task.Yield();
    }

    return sum;
}

As a JS programmer I can understand what they did here.

BUT as a C# programmer I ask myself : why not open a task for it ?

 public static async Task < int > FindSeriesSum(int i1)
    {
         //do something....
         return await MyLongCalculationTask();
         //do something
    }

Question

With Js I can't open a Task (yes i know i can actually with web workers) . But with c# I can.

If So -- why even bother with releasing from time to time while I can release it at all ?

Edit

Adding references :

From here : enter image description here

From here (another ebook):

enter image description here


  • That's a poor example, because Task.Yield won't keep the UI responsive. Also, async and await do not free up the UI thread; if you have a long calculation, you'll need to use Task.Run. - Stephen Cleary
  • @StephenCleary Considering a multi threaded app (ASP.NET or similar) where a task is already being executed on the thread pool and the thread pool is a valuable resource: If a long running CPU intensive task would call await Task.Yield() every second then other short running tasks should get a chance without pending in the queue for a long time. Wouldn't this be fair behavior? - springy76
  • @springy76: The preemptive thread switching built into every modern OS combined with the self-adjusting thread pool in .NET will work far better than a manual await Task.Yield(). - Stephen Cleary

6 답변


49

When you see:

await Task.Yield();

you can think about it this way:

await Task.Factory.StartNew( 
    () => {}, 
    CancellationToken.None, 
    TaskCreationOptions.None, 
    SynchronizationContext.Current != null?
        TaskScheduler.FromCurrentSynchronizationContext(): 
        TaskScheduler.Current);

All this does is makes sure the continuation will happen asynchronously in the future. By asynchronously I mean that the execution control will return to the caller of the async method, and the continuation callback will not happen on the same stack frame.

When exactly and on what thread it will happen completely depends on the caller thread's synchronization context.

For a UI thread, the continuation will happen upon some future iteration of the message loop, run by Application.Run (WinForms) or Dispatcher.Run (WPF). Internally, it comes down to the Win32 PostMessage API, which post a custom message to the UI thread's message queue. The await continuation callback will be called when this message gets pumped and processed. You're completely out of control about when exactly this is going to happen.

Besides, Windows has its own priorities for pumping messages: INFO: Window Message Priorities. The most relevant part:

Under this scheme, prioritization can be considered tri-level. All posted messages are higher priority than user input messages because they reside in different queues. And all user input messages are higher priority than WM_PAINT and WM_TIMER messages.

So, if you use await Task.Yield() to yield to the message loop in attempt to keep the UI responsive, you are actually at risk of obstructing the UI thread's message loop. Some pending user input messages, as well as WM_PAINT and WM_TIMER, have a lower priority than the posted continuation message. Thus, if you do await Task.Yield() on a tight loop, you still may block the UI.

This is how it is different from the JavaScript's setTimer analogy you mentioned in the question. A setTimer callback will be called after all user input message have been processed by the browser's message pump.

So, await Task.Yield() is not good for doing background work on the UI thread. In fact, you very rarely need to run a background process on the UI thread, but sometimes you do, e.g. editor syntax highlighting, spell checking etc. In this case, use the framework's idle infrastructure.

E.g., with WPF you could do await Dispatcher.Yield(DispatcherPriority.ApplicationIdle):

async Task DoUIThreadWorkAsync(CancellationToken token)
{
    var i = 0;

    while (true)
    {
        token.ThrowIfCancellationRequested();

        await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);

        // do the UI-related work item
        this.TextBlock.Text = "iteration " + i++;
    }
}

For WinForms, you could use Application.Idle event:

// await IdleYield();

public static Task IdleYield()
{
    var idleTcs = new TaskCompletionSource<bool>();
    // subscribe to Application.Idle
    EventHandler handler = null;
    handler = (s, e) =>
    {
        Application.Idle -= handler;
        idleTcs.SetResult(true);
    };
    Application.Idle += handler;
    return idleTcs.Task;
}

It is recommended that you do not exceed 50ms for each iteration of such background operation running on the UI thread.

For a non-UI thread with no synchronization context, await Task.Yield() just switches the continuation to a random pool thread. There is no guarantee it is going to be a different thread from the current thread, it's only guaranteed to be an asynchronous continuation. If ThreadPool is starving, it may schedule the continuation onto the same thread.

In ASP.NET, doing await Task.Yield() doesn't make sense at all, except for the workaround mentioned in @StephenCleary's answer. Otherwise, it will only hurt the web app performance with a redundant thread switch.

So, is await Task.Yield() useful? IMO, not much. It can be used as a shortcut to run the continuation via SynchronizationContext.Post or ThreadPool.QueueUserWorkItem, if you really need to impose asynchrony upon a part of your method.

Regarding the books you quoted, in my opinion those approaches to using Task.Yield are wrong. I explained why they're wrong for a UI thread, above. For a non-UI pool thread, there's simply no "other tasks in the thread to execute", unless you running a custom task pump like Stephen Toub's AsyncPump.

Updated to answer the comment:

... how can it be asynchronouse operation and stay in the same thread ?..

As a simple example: WinForms app:

async void Form_Load(object s, object e) 
{ 
    await Task.Yield(); 
    MessageBox.Show("Async message!");
}

Form_Load will return to the caller (the WinFroms framework code which has fired Load event), and then the message box will be shown asynchronously, upon some future iteration of the message loop run by Application.Run(). The continuation callback is queued with WinFormsSynchronizationContext.Post, which internally posts a private Windows message to the UI thread's message loop. The callback will be executed when this message gets pumped, still on the same thread.

In a console app, you can run a similar serializing loop with AsyncPump mentioned above.


  • Noseration , hi :-) . You said : "There is no guarantee it is going to be a different thread from the current thread, it's only guaranteed to be an asynchronous continuation" . — But if it would stay on the same thread - the operation become synchronous operation. no ? in other words , how can it be asynchronouse operation and stay in the same thread ? asynchrounsey is about continuations - meaning "I will come back and continue where it stopped". but if it is in the same thread , it is more likely it never left the oprtaion.....no ? - Royi Namir
  • Hey @RoyiNamir, it's quite possible to continue asynchronously on the same thread, check my update. - noseratio
  • Yes :) , but you wrote the comment in the non-UI thread section , which makes me confusion..... - Royi Namir
  • @RoyiNamir, you can do that on a non-UI thread too, with the magic of SynchronizationContext. Have you looked at AsyncPump? After all, asynchrony doesn't assume multi-threading. It's just something that will deliver a result in the future. Note, even when you deal with ThreadPool without any s.context, there's a chance that the continuation will happen on the same thread (e.g., the same pool thread may start and later serve the completion of an async I/O operation, by coincidence). - noseratio
  • @bN_, there is: protected virtual Task OverridableMethod() { return Task.Task.CompletedTask; }. Note async is not a part of the virtual method signature, so you still can use it when overriding this method in a derived class. - noseratio

16

I've only found Task.Yield useful in two scenarios:

  1. Unit tests, to ensure the code under test works appropriately in the presence of asynchrony.
  2. To work around an obscure ASP.NET issue where identity code cannot complete synchronously.


6

No, it's not exactly like using setTimeout to return control to the UI. In Javascript that would always let the UI update as the setTimeout always has a minimum pause of a few milliseconds, and pending UI work has priority over timers, but await Task.Yield(); doesn't do that.

There is no guarantee that the yield will let any work be done in the main thread, on the contrary the code that called the yield will often be prioritised over UI work.

"The synchronization context that is present on a UI thread in most UI environments will often prioritize work posted to the context higher than input and rendering work. For this reason, do not rely on await Task.Yield(); to keep a UI responsive."

Ref: MSDN: Task.Yield Method


  • IF not relying on it - so for what it is there ? can you supply REAL example ? - Royi Namir
  • @RoyiNamir: It's there for handling multithreading, but not in a way that is reliable for keeping the UI responsive. It makes the method asynchronous, but it doesn't keep it from competing with the main thread for CPU. - Guffa
  • If it may win the main thread priority , then why would i want to use it ? What you are saying is that the code may run sync and not async - Royi Namir
  • @RoyiNamir: The code runs asyncronously, but you may get the impression that it runs syncronously because of the way that the work is prioritised. Async tasks are more suited for work that is waiting for something to happen, for CPU intensive work you would rather look into the AsParallel method. - Guffa

2

First of all let me clarify: Yield is not exactly the same as setTimeout(function (){...},0);. JS is executed in single thread environment, so that is the only way to let other activities to happen. Kind of cooperative multitasking. .net is executed in preemptive multitasking environment with explicit multithreading.

Now back to Thread.Yield. As I told .net lives in preemptive world, but it is a little more complicated than that. C# await/async create interesting mixture of those multitasking mode ruled by state machines. So if you omit Yield from your code it will just block the thread and that's it. If you make it a regular task and just call start (or a thread) then it will just do it's stuff in parallel and later block calling thread when task.Result is called. What happens when you do await Task.Yield(); is more complicated. Logically it unblocks the calling code (similar to JS) and execution goes on. What it actually does - it picks another thread and continue execution in it in preemptive environment with calling thread. So it is in calling thread until first Task.Yield and then it is on it's own. Subsequent calls to Task.Yield apparently don't do anything.

Simple demonstration:

class MainClass
{
    //Just to reduce amont of log itmes
    static HashSet<Tuple<string, int>> cache = new HashSet<Tuple<string, int>>();
    public static void LogThread(string msg, bool clear=false) {
        if (clear)
            cache.Clear ();
        var val = Tuple.Create(msg, Thread.CurrentThread.ManagedThreadId);
        if (cache.Add (val))
            Console.WriteLine ("{0}\t:{1}", val.Item1, val.Item2);
    }

    public static async Task<int> FindSeriesSum(int i1)
    {
        LogThread ("Task enter");
        int sum = 0;
        for (int i = 0; i < i1; i++)
        {
            sum += i;
            if (i % 1000 == 0) {
                LogThread ("Before yield");
                await Task.Yield ();
                LogThread ("After yield");
            }
        }
        LogThread ("Task done");
        return sum;
    }

    public static void Main (string[] args)
    {
        LogThread ("Before task");
        var task = FindSeriesSum(1000000);
        LogThread ("While task", true);
        Console.WriteLine ("Sum = {0}", task.Result);
        LogThread ("After task");
    }
}

Here are results:

Before task     :1
Task enter      :1
Before yield    :1
After yield     :5
Before yield    :5
While task      :1
Before yield    :5
After yield     :5
Task done       :5
Sum = 1783293664
After task      :1
  • Output produced on mono 4.5 on Mac OS X, results may vary on other setups

If you move Task.Yield on top of the method it will by async from the beginning and will not block the calling thread.

Conclusion: Task.Yield can make possible to mix sync and async code. Some more or less realistic scenario: you have some heavy computational operation and local cache and task CalcThing. In this method you check if item is in cache, if yes - return item, if it is not there Yield and proceed to calculate it. Actually sample from your book is rather meaningless because nothing useful is achieved there. Their remark regarding GUI interactivity is just bad and incorrect (UI thread will be locked until first call to Yield, you should never do that, MSDN is clear (and correct) on that: "do not rely on await Task.Yield(); to keep a UI responsive".


  • The analogy to JS was in context where GUI apps in c# has a single main thread which runs the UI -- same as JS single loop. ( and in JS - the the solution is setTimeout(...,0 ) - Royi Namir
  • love the demo :) - Noctis

0

You're assuming the long-running function is one that can run on a background thread. If it isn't, for example because it has UI interaction, then there is no way to prevent blocking the UI while it runs, so the times it runs should be kept short enough not to cause problems for the users.

Another possibility is that you have more long-running functions than you have background threads. In that scenario, it may be better (or it may not matter, it depends) to prevent a few of those functions from taking up all of your threads.


  • Your first case is potentially valid, but the second really isn't. If you have more work, or even workers, than you have threads, then they'll simply need to share that time. Giving some of that work to the UI thread isn't helpful there. If you didn't, it would simply mean the UI thread would be idling, thus yielding that time to worker threads. - Servy
  • @hvd By saying UI interaction - you are talking about a code which can appear (Before || After) the await. right ? - Royi Namir
  • @hvd You need to have many more operations than you have threads, for that to happen. It is called thread starvation. In all but the most extreme cases, the thread's scheduler is capable of solving the problem by changing the execution on each thread, allowing each short slices of time to run, rather than running each operation to completion. On top of that, the problem can be solved much more effectively through the use of a thread pool, which can have exactly as many workers as is optional, and queue up the actual work to be done in such a way as to optimize throughput. - Servy
  • @hvd No, that is not something that will cause a problem, because the thread's schedulers are not that poor, and the amount of time that the UI thread would need to be starved out for a human to perceive the lag is quite high. Other threads don't have the option of holding onto the CPU time forever; in fact, they have almost no control over the matter at all. It is not a cooperative system, unlike management of the UI thread from within the UI thread in which someone can intentionally hog the UI thread for as long as they want, starving everyone else out. - Servy
  • @hvd Technically that's just one way of accomplishing the second option that I mentioned, it's just a fairly poorly designed way of accomplishing it. If a task is well suited to being broken up, then break it up. If it's not, just stick LongRunning on it rather than yield all over the place. - Servy

0

I think that nobody provided the real answer when to use the Task.Yield. It is mostly needed if a task uses a never ending loop (or lengthy synchronous job), and can potentially hold a threadpool thread exclusively (not allowing other tasks to use this thread). This can happen if inside the loop the code runs synchronously. the Task.Yield reschedules the task to the threadpool queue and the other tasks which waited for the thread can be executed.

The example:

  CancellationTokenSource cts;
  void Start()
  {
        cts = new CancellationTokenSource();

        // run async operation
        var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
        // wait for completion
        // after the completion handle the result/ cancellation/ errors
    }

    async Task<int> SomeWork(CancellationToken cancellationToken)
    {
        int result = 0;

        bool loopAgain = true;
        while (loopAgain)
        {
            // do something ... means a substantial work or a micro batch here - not processing a single byte

            loopAgain = /* check for loop end && */  cancellationToken.IsCancellationRequested;
            if (loopAgain) {
                // reschedule  the task to the threadpool and free this thread for other waiting tasks
                await Task.Yield();
            }
        }
        cancellationToken.ThrowIfCancellationRequested();
        return result;
    }

    void Cancel()
    {
        // request cancelation
        cts.Cancel();
    }


  • From the second thought, you could probably use it like that, but it doesn't feel like a good design: queuing a large pile of CPU-bounds tasks to the thread pool (more than it can handle), so they have to yield with Task.Yield() to give their peers a chance to execute. There are better options for concurrent CPU-bound scenarios like this, e.g. Parallel or TPL Dataflow. - noseratio
  • @noseratio The possible usage of this pattern if the code in the loop executes a user defined handler. The code does not know how it behaves - if it is async or synchronous. So as a precaution, in the end of the loop it is better to have a Task.Yield. This is common in message handling buses. When the bus instantiate handlers and process messages. If it has a lot of workers which run in parallel, then it can exaust the threadpool threads. I used it implementing worker coordinator in github.com/BBGONE/REBUS-TaskCoordinator - Maxim T
  • And one more thing - this can be tested. I have a TaskCoordinator test lab github.com/BBGONE/TaskCoordinator which uses this pattern. Without using Task.Yield if you configure to run a lot of tasks in parallel to process messages, then you won't get messages in the console to appear - because all pool threads are busy, and threadpool does not add more - because of the high CPU usage. But when using Task.Yield - the status messages appear in the console. And it processes around 200 000 messages per second, and when there was less overhead (no serialization) - around 500 000 per second. - Maxim T
  • I don't think using Task.Yield to overcome ThreadPool starvation while implementing producer/consumer pattern is a good idea. I suggest you ask a separate question if you want to go into details as to why. - noseratio
  • @noseratio sounds like you're up :) stackoverflow.com/questions/53263258/… - Marc Gravell

Linked


Related

Latest