Task.Yield - real usages?

前端 未结 6 2062
-上瘾入骨i
-上瘾入骨i 2020-12-01 05:29

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 setTime

6条回答
  •  我在风中等你
    2020-12-01 05:39

    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> cache = new HashSet>();
        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 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".

提交回复
热议问题