Tasks, Parallelism and why I'm learning to love Unit Testing
Since the last blog post there has been some nice progress on where I feel I am, with several Umbraco Projects close to completion I've been left to do more revising and more in-depth learning with C# in preparation for taking on the Microsoft C# Exam for a rematch.
Microsoft, quite thankfully, tell you where you lacking when you fail an exam and recommends some important areas for you to improve.
Which brings me to one half of what I'm talking about today.
Given what I've learned in the interval, it's shocking to me that I went into the last exam with such a lack of understanding about Tasks, both about how they can be used and with how they were implemented.
A Task, in essence, is a single operation that doesn't return a value (clarification on this later) and can be executed asynchronously via the thread pool as opposed to on the main application thread. This allows greater control and information in regards to controlling and understanding how an application is running as you can check the status of a task throughout it's execution with less coding.
Tasks can return values but must be declared with the returned type as a parameter. For example a task that returns void is defined as just that, a Task(). Whereas a task designed to return a string would be declared as Task<string>().
It would amiss to discuss tasks without touching on the "async" and "await" keywords of C#. "async" defines an operation that is handled asynchronously, this operation can then wait on other operations to finish before it continues, helping to control the flow of an application.
The purpose "await" is exactly what I mentioned above, an awaited task can be finished before the work continues meaning that long running and expensive operations can be waited for without "freezing" the application.
Tasks have many use cases and are part of a greater school of design; Parallelism.
This is an interesting and deep topic to talk about, so I'll keep it to some essentials.
Parallelism allows several operation to be executed concurrently, for example, starting many tasks at once in order to process a large set of data quickly. However, using this functionality incurs and overhead where data is handed out to several different threads at once and then recombined at the end. If the load of work is too small, you can lose performance as the windup to parallel invocation and then execution and wind-down can be slower than simply handling the work sequentially.
I'll admit that my revision and study into the topic still leaves something to be desired, but as the projects I work on grow in complexity, it's another tool to help improve the quality of what I can create. Through my reading on the subject, it's clear that Parallelism is going to become increasingly more efficient and therefore more useful as time goes on. It's better to be prepared and able to take advantage of it now, than playing catch up later down the line.
Oh boy, this is something that has gotten me excited the more I've read into it and the reason why will require some explanation.
Ever since I started working at Antelle, I've found the best way to learn and provide myself with revision material later on, is to create what I call a "Playground", these are small projects, in C# they have been almost exclusively small Console Application that then let me take a new concept I'm trying to understand and learn and create a simple, carefully commented application so that if I ever need to remind myself of an example implementation of anything. I have some example code that I've written for myself.
Which brings me onto Unit Testing, this is something I've only strictly got myself to do for the last few months. To put it generically, a unit test requires a section of code that should then produce what is expected to be produced. The most basic of code tests would for example be to program a C# method that adds two numbers together. This result of this method would be bound to a variable, the "actual result". It is then tested to see if it matches up the "expected result". If it does, then the test would Assert the result to be correct and the test is passed. If it does not, the test Asserts that the result is incorrect and the test would fail.
This allows you to ensure that your code is working as designed and then you can confidence in going into actually programming the logic of your application. If errors occur, you can check your tests to immediately narrow down where the source of the problem can be.
With my playgrounds, it means that I can write unit tests to see if my implementation is working correctly and you can run as many tests as you like, the tests themselves in turn can be as complex as you like as well. While I work with these tests they can still be commented for my own later review without missing any of the context and all the while I can write and debug code that doesn't have "Console.WriteLine()" everywhere as means of knowing what values are and what my code is doing.
Overall, I've found Unit Testing a huge boon to how I think about new code, it means less time spent debugging code that I write and assurance about continuing with developing a project in the knowledge that as long as tests that reflect core use cases of the code are passed without error. That the error lies outside the test cases.
All in all
There have been a few projects recently, mostly Umbraco websites that I'm finding more routine to design and build to the customer's needs. For now, I can focus on revising and being as ready for my exam as possible, I'm making new "Playgrounds" to help with that and I feel much more ready than my last attempt.
Until the next time, thanks for reading.