Introduction
There are some methods to release current thread excution right in .NET/C#. They are Thread.Sleep(0),Task.Delay(0),Thread.Yield(),Task.Yeild(). The function of these methods is to tell the program to give up current thread and then run another thread. But there are some differences among these methods.
This article is about their differences and theories.
Detail
The original link is C#/.NET 中 Thread.Sleep(0), Task.Delay(0), Thread.Yield(), Task.Yield() 不同的执行效果和用法建议. But this blog was written in Chinese, so I translate its content to English. Waterlv is an MVP(Microsoft Most Valuable Professional), and he is good at .NET Core\WPF\.NET. Here is his Blog.
Theories
Thread.Sleep(0)
Firstly,I will post the source code of Thread.Spleep(int millisecondsTimeout).1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/*=========================================================================
** Suspends the current thread for timeout milliseconds. If timeout == 0,
** forces the thread to give up the remainer of its timeslice. If timeout
** == Timeout.Infinite, no timeout will occur.
**
** Exceptions: ArgumentException if timeout < 0.
** ThreadInterruptedException if the thread is interrupted while sleeping.
=========================================================================*/
[] // auto-generated
[]
[]
private static extern void SleepInternal(int millisecondsTimeout);
[] // auto-generated
public static void Sleep(int millisecondsTimeout)
{
SleepInternal(millisecondsTimeout);
// Ensure we don't return to app code when the pause is underway
if(AppDomainPauseManager.IsPaused)
AppDomainPauseManager.ResumeEvent.WaitOneWithoutFAS();
}
It is easy to find that in the Sleep method it calls another method which’s name is SleepInternal. SleepInternal is implemented in CLR, and its function is suspending the current thread for the value of millisecondsTimeout.
If we set the value of milliseconsTimeout to 0 as Thread.Sleep(0).It will force the current thread to give up the rest of the CPU time slice. Then other threads which have higher priority will run. But if there are not any available threads have higher priority than current thread, this thread will keep running.
If your method will not be affected by other threads, there are not any differences among the methods above. But when your method is affected by multiple threads, other threads may run into this method when you call Thread.Sleep(0).But it is good news that the CPU time slice for a thread is nanosecond level, so this situation is almost impossible to happen.
Thread.Yeild()
Here is the code of Thread.Yeild()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15[] // auto-generated
[]
[]
[]
[
]
private static extern bool YieldInternal();
[] // auto-generated
[
]
public static bool Yield()
{
return YieldInternal();
}
And we can notice that Yield() method calls YieldInternal() method. And YieldInternal is implemented in CLR too.
The function of Thread.Yield() is to force the current thread to give up the rest of CPU time slice, the same as Thread.Sleep(0).
Thread.Sleep(1)
It is just a little differance between Thread.Sleep(1) and Thread.Sleep(0), but their function are different.
The current thread will be suspended for timeout milliseconds(here is 1ms). Therefore, during timeout milliseconds current thread will stay at the un-schedulable state. So, other threads will be run even their priority is lower than the current thread.
Here is the result of these three method’s execution time.
Nothing means there are not any codes. And we used Stopwatch to get the result. For more detail about Stopwatch visit .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间). This blog was written in Chinese, I will translate it later.
Here is a part of the codes.
1 | var stopwatch = Stopwatch.StartNew(); |
Task.Delay(0)
Task.Delay is a method of TAP. For more informations about TAP,visit Task-based Asynchronous Pattern (TAP) Microsoft Docs.
TAP is a threading model which bases on an async state machine, and this is the biggest difference from the Thread method.
Here is the source code of Task.Delay.
1 | /// <summary> |
This method will return Task.CompleteTask as a result when the value of the parameter is 0. That means the code you write after Task.Delay(0) will be executed immediately. (If the rest of CPU time slice is enough).
Task.Yield()
The result of Task.Yield() is a YeildAwaitable instance and the YieldAwaitable.GetAwaiter method returns an instance of YieldAwaiter. So, the result of Task.Yield method is fully depending on YieldAwaiter. For more details about Awaiter visit 如何实现一个可以用 await 异步等待的 Awaiter.
YieldAwaiter relies on QueueContinuation to determine when to execute subsequent codes. Part of the source codes is listed below.
1 | // Get the current SynchronizationContext, and if there is one, |
There are two branches in these codes.
One is setting
DispatcherSynchronizationContextas the value ofSynchronizationContext, then it will callPostmethod inSynchronizationContextto execute next asynchronous task. The ‘continuation’ means executes the next asynchronous task.
The value ofSynchronizationContextis set asDispatcherSynchronizationContextfor WPF UI thread, and itsPostmethod is designed to implement the message loop. If other threads have no special setting, the value ofSynchronizationContextis alwaysnull. For more information,see 出让执行权:Task.Yield, Dispatcher.Yield.If the value of
DispacherSynchronizationContextisnullorSynchronizationContexttype, the codes above will runelselogic. And its logic is depending on the value ofTaskScheduler.Current, it will find next thread in thread pool or start aTaskagain.
Task.Delay(1)
Task.Delay(1) is almost the same as Task.Delay(0), but the function of these two codes is different.
Task.Delay(1) starts a System.Threading.Timer instance, and it will describe a callback method which is executed when time is up.
Here is the exact API order to excute the callback
Timer.TimerSetup->TimerHolder->TimerQueueTimer->TimerQueue.UpdateTimer->EnsureAppDomainTimerFiresBy->ChangeAppDomainTimer->callback
The codes after await will be encapsulated by the asynchronous state machine and passed to the callback above.
Here is the code of ChangeAppDomainTimer method.1
2
3
4
5[]
[]
[]
[]
static extern bool ChangeAppDomainTimer(AppDomainTimerSafeHandle handle, uint dueTime);
And when we call the methods in Thread, they just affect the scheduling status of the current thread. But when we call the methods in Task, they will affect the scheduling of the thread pool, then they will call System.Threading.Timer to count time, the time they consume is more uncontrollable.
Here is the result of the time they consumed.
Nothing means there are not any codes.
Differences
The function of Thread.Sleep(0) and Thread.Yield() is the same in thread scheduling. And Thread.Sleep(int) will wait for time out and it bases on thread scheduling.
Thread.Sleep(0) and Thread.Yield() can be called for giving up current thread’s rest time slice, and give chance to run other threads. If you want to make your thread wait, you can call Thread.Sleep(int).
If you have to use async/await , you should use Task.Delay(0) or Task.Yield(). If you use Task.Delay instead of Thread.Sleep, it can save resources for one thread.
References
- Thread.Sleep(0) vs Sleep(1) vs Yeild - stg609 - 博客园
- c# - Task.Delay().Wait(); sometimes causing a 15ms delay in messaging system - Stack Overflow
- c# - When to use Task.Delay, when to use Thread.Sleep? - Stack Overflow
- c# - Should I always use Task.Delay instead of Thread.Sleep? - Stack Overflow
- What’s the difference between Thread.Sleep(0) and Thread,Yield()?