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
DispatcherSynchronizationContext
as the value ofSynchronizationContext
, then it will callPost
method inSynchronizationContext
to execute next asynchronous task. The ‘continuation’ means executes the next asynchronous task.
The value ofSynchronizationContext
is set asDispatcherSynchronizationContext
for WPF UI thread, and itsPost
method is designed to implement the message loop. If other threads have no special setting, the value ofSynchronizationContext
is alwaysnull
. For more information,see 出让执行权:Task.Yield, Dispatcher.Yield.If the value of
DispacherSynchronizationContext
isnull
orSynchronizationContext
type, the codes above will runelse
logic. And its logic is depending on the value ofTaskScheduler.Current
, it will find next thread in thread pool or start aTask
again.
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()?