Task.Yield - real usages?

六月ゝ 毕业季﹏ 提交于 2019-12-17 09:19:09

问题


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 :

From here (another ebook):


回答1:


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.




回答2:


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.



回答3:


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




回答4:


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".




回答5:


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.




回答6:


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();
    }


来源:https://stackoverflow.com/questions/23431595/task-yield-real-usages

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!