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
await to solve the thread issue doesn't come without its own list of problems. Using
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
await calls while not interfering with the UI thread.
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
Run method once, preferably after UI initialization.
This call sets up the coroutine loop, and also a
Time loop that keeps track of
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.
public partial class MainWindow : Window
private IEnumerator CountDown()
int counter = 0;
while(counter < 10)
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