Transfer of execution rights - Task.Yield, Dispatcher.Yield

Introduction

As the word Yield means, the Yield() method transfers the execution right of the current task, after that other tasks may get the right to execute. And the method of Yield() is existed in Task, Dispatcher, Thread class, and as the name shows, all of these methods are to transfer the current task’s execution right.

Are there any differences? How do they work?

In this article, we will see more details about the Yield() methods.

Details

The original link is 出让执行权:Task.Yield, Dispatcher.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.


Dispatcher.Yield

If we have to implement a time-consuming method, what methods or API will you choose to avoid affecting the UI thread? Maybe Invoke and InvokeAsync is a good choice, they split the time-consuming task into small pieces that are executed at a lower priority than user input and rendering.

Dispatcher.Yield() does the same job, and its function is more familiar with Dispatcher.InvokeAsync than Dispatcher.Invoke.

It needs await before the method when we use it.

1
2
3
4
5
foreach(var item in collection)
{
DoWorkWhichWillTakeHalfASecond();
await Dispacther.Yield();
}

As the codes above, it splits the task to small pieces every time when executing foreach method. Therefore, the UI thread can respond to user interaction and rendering when running these time-consuming codes.

We can pass a parameter to Yield method when we use it, the value of this parameter is the priority of the subsequent task. Its default value is DispatcherPriority.Background, and priority of Background is lower than the user’s input action’s priority (DispatcherPrority.Input), UI’s priority (DispatcherPriority.Loaded) and rendering priority (DispatcherPriority.Render).

How does Dispatcher.Yield method work?

After reading the source codes of Dispatcher.Yield, it can find that the result of this method is an instance of DispatcherPriorityAwaiter, and its OnCompleted method is posted below.

1
2
3
4
5
6
public void OnCompleted(Action continuation)
{
if(_dispatcher == null)
throw new InvalidOperationException(SR.Get(SRID.DispatcherPriorityAwaiterInvalid));
_dispatcher.InvokeAsync(continuation, _priority);
}

We can find that this OnCompleted method calls the InvokeAsync method. It depends on InvokeAsync to implement its function.

Visit 【C#】【多线程】【05-使用C#6.0】08-自定义awaitable类型 - L.M, for more information about OnCompleted method.

Tips about Dispatcher.Yield()

Dispatcher.Yield is a static method of class Dispatcher, and InvokeAsync is a normal method.
Here is an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
using System.Windows.Threading

class Demo : DispatcherObject
{
void Test()
{
// this 'Dispatcher' is class and Yield() is a static method of Dispatcher class.
await Dispatcher.Yield();

// this 'Dispatcher' is an instance of 'Dispatcher' class, and the name of this instance is Dispatcher.
await Dispatcher.InvokeAsync(()=>{})
}
}

Task.Yield

We use Task.Yield method instead of Dispatcher.Yield in the example posted above.

1
2
3
4
5
foreach(var item in collection)
{
DoWorkWhichWillTakeHalfASecond();
await Task.Yield();
}

This method has the same effect as Dispatcher.Yield(DispatcherPriority.Normal). Task depends on SynchronizationContext to schedule threads, and the WPF UI thread’s SynchronizationContext is set to DispatcherSynchronizationContext, it relies Dispatcher to schedule; When creating an instance of DispatcherSynchronizationContext its default priority is Normal, and WPF doesn’t pass a special value. So, after calling Task.Yield on WPF UI thread to transfer execution right, it uses Normal priority to recover. Therefore, its effect is the same as Dispatcher.Yield(DispatcherPriority.Normal).

Here is the code about how does DispatcherSynchronizationContext execute subsequent task.

1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>
/// Asynchronously invoke the callback in the SynchronizationContext.
/// </summary>
public override void Post(SendOrPostCallback d, Object state)
{
// Call BeginInvoke with the cached priority. Note that BeginInvoke
// preserves the behavior of passing exceptions to
// Dispatcher.UnhandledException unlike InvokeAsync. This is
// desireable because there is no way to await the call to Post, so
// exceptions are hard to observe.
_dispatcher.BeginInvoke(_priority, d, state);
}

Due to Task.Yield‘s Normal priority, its effect on the UI thread is not as good as Dispatcher.Yield. But, it is important to note that SynchronizationContext is not related to Dispatcher, so Task.Yield can be called on every thread. And it will be a good choice to use Task.Yield to split the time-consuming task into small tasks in the Task method.

References