Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Implementing Unity's Coroutines on Winforms and WPF Applications

5.00/5 (4 votes)
6 Jun 2017CPOL3 min read 14.1K   440  
In this article, we show how you can execute asynchronous code on the main thread without async, by replicating Unity's coroutine framework.

Introduction

The general problem a user is faced with when developing a Winforms or a WPF application, is that the UI of those frameworks is single threaded.

Running asynchronous code, while possible via secondary threads, leaves the user unable to make UI calls on the main thread, without using Invoke workarounds.

The problem is aggravated if the user needs to execute delayed code, since Thread.Sleep hangs the entire thread, making the UI unresponsive.

The use of async/await to solve the thread issue doesn't come without its own list of problems. Using await/async on any asynchronous methods creates async dependencies propagating to all methods calling the current one, and all methods the current one calls.

Pretty fast, the entire code is infested with async methods, even though the user only wanted one of them to be asynchronous.

The game engine Unity, took the idea of creating Coroutines through the use of IEnumerable collections, and fleshed it out, and based the entire engine around it.

Through slicing, we can achieve a virtually asynchronous parallel execution on the main thread. This prevents the creation of additional threads, and keeps our code clean from redundant async/await calls while not interfering with the UI thread.

Background

For anyone unfamiliar with the use of IEnumerator Coroutines, Unity has a brief overview on their website, as seen here.

Using the Code

The main challenge in implementing Unity's Coroutine framework, is that it requires a game loop. Slicing is frame based, and requires constantly executed iterative code in order to traverse all registered coroutines.
Winforms and WPF however do not have an exposed Main loop. They are both event driven frameworks based on registered delegates.

On Winforms, generating something like a game loop proved especially tricky. Eventually, we decided to follow Tom Miller's approach, described in his blog post here.

His blog describes how we can approximate a continuous loop on the main thread by waiting for all thread operations to end, and then looping continuously until a new operation requires to use the thread.

This approach, while working adequately, does create a spin, and therefore can be CPU intensive. The only thing we had to change from that implementation, was the added Refresh() on the main form. This was necessary since the thread goes completely inactive if no controls are being modified on it.

For WPF, the main loop was easier to simulate. WPF gives us an OnRender event through CompositionTarget.Rendering. All that was needed to be done was to subscribe the main loop callback to that event.


This means that since no spins are generated, the WPF implementation of the Coroutine Framework is much less CPU intensive than its Winforms counterpart.

Implementing the framework is very simple. All Coroutine related classes are in the UnityCoroutines namespace. After importing it, all that needs to be done is to call the CoroutineManager singleton's Run method once, preferably after UI initialization.

C#
//
// Winforms Initialization
//
// "this" refers to the current Form.
// We pass it to the singleton to run the Refresh() call on it.

CoroutineManager.Instance.Run(this);
C#
//
// WPF Initialization
//
CoroutineManager.Instance.Run();

This call sets up the coroutine loop, and also a Time loop that keeps track of deltaTime, unscaledDeltaTime, time and timeScale variables. These variables are required in order to perform time-relevant asynchronous execution.

Just like in Unity, we initialize and run a coroutine through StartCoroutine, by providing it as an argument in the form of an IEnumerator. This coroutine is then sliced and executed sequentially in parallel to the UI code.

Unlike Unity however, StartCoroutine, as with all Coroutine related methods, exists in the CoroutineManager singleton and must be executed through it.

The following code (which also exists in Form1.cs and MainWindow.xaml.cs, shows an example of a timer running in parallel to the UI thread, increasing its counter by 1 every 5 seconds.

C#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        //Initialization of Coroutine Framework
        CoroutineManager.Instance.Run();
        
        //First coroutine call
        CoroutineManager.Instance.StartCoroutine(CountDown());
    }

    private IEnumerator CountDown()
    {
        int counter = 0;
        while(counter < 10)
        {
            counter++;
            Counter.Content = "Counter : " + counter;                
            yield return CoroutineManager.Instance.StartCoroutine(WaitForSeconds(5f));
        }
    }

    private IEnumerator WaitForSeconds(float seconds)
    {
        float time = 0f;
        while(time < seconds)
        {
            EllapsedSeconds.Content = "Elapsed Seconds : " + time;
            DeltaTime.Content = "Delta Time : " + Time.deltaTime;
            time += Time.deltaTime;
            yield return null;
        }
    }
}

Notice that just as in Unity, we can get the return of the StartCoroutine method and yield it as well, in essence pausing execution until the called coroutine completes its own execution.

Points of Interest

The framework can be expanded. A YieldInstruction interface has been created, and the CoroutineManager shows where its use should be. Therefore, classes properly implementing the YieldInstruction interface can be yielded by the CoroutineManager.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)