It is said that Countess Lovelace was the first programmer. Personally, I think that Mother Nature was the first programmer, and the popular “observer” was one of the first design patterns created by her.
Let’s take a look at its definition:“A one-to-many relationship between objects created in such a way in which one object changes its status, all its observers are informed about it and can react to these changes in a most appropriate manner.”
A simple example: when you hit your knee, the central nervous system will inform all its subscribers about it. What can be such a subscriber? For example, the lacrimal gland, which at the right amount of impact will cause a tear to flow. It can also be our brain that will inform the muscles to touch a knee or say certain words. The idea of a pattern is therefore very simple.
If you know what events are and how to deal with them, you can move straight to chapter 2 to learn how to be up to date with events.
At first glance, work with .NET events seems to be very easy. Actually, we must know only two operators += and -=, while the second one is being forgotten by us, the programmers. But let’s move accordingly. We already know that we must have an informing object and a subscriber. Let’s assume that a button will be our observable, subject. The button will have a Clicked field of EventHandler type with a keyword – event.
In order for our button to inform us that it has been clicked, we need to subscribe to our Clicked event as in Initialize() method.
private void Initialize()
PlayButton_Clicked method will be activated every time when someone clicks the button in our window. We must remember that Garbage Collector will not “clean” a playButton, if we do not discontinue to observe its events – we may make it as follows:
There is, however, another problem – when and how to remove a subscription. But this is not a topic of the today’s article.
2. Async over events
Therefore, a model of work with events is as follows – we know that a certain thing may happen in the future, but we do not know exactly when. Data collection is the first thing that comes to my mind based on the description above. We start to download a certain set of data and subscribe to DownloadCompleted, DownloadFailed and DownloadCancelled events. The scenario is very simple, as the data is collected from one source only and only this data will be displayed in our view. Many of the handled events should not cause a problem if our class does not look like spaghetti. And what about a slightly more complicated process?
Imagine that we are in a bit older project. To inform about the download status, we use events and we collect data from several different services – always independently. Business comes and wants to suddenly collect data from B service based on those collected from A service – for example, calculation of shipment costs based on the weight of ordered goods. This would look as bad as below. And this is the case when only one service is dependent on the other one!
Let’s go a step further and set a new requirement – data download from many services, putting them into one object and displaying in the form to a user. This may be done in a very easy way. Fields of our element may be completed completely asynchronously, which would translate into a tragic experience of using our application. And what if one of the fields was calculated on the basis of others? How would a user be confused if the amount changed at random intervals without much explanation?
For sure, the best solution in this situation would be to rewrite the method of downloading and using Task and await keyword. Unfortunately, this type of solutions is not always possible and we must handle such issues in another way.
At the premiere of .NET 4.0, we are now able to use a huge library of Task Parallel Library (TPL) and famous async and await. Within the space of names of System.Threading. Tasks there is a solution for the above mentioned issues – a class called TaskCompletionSource<TResult>, which is a “producer” of Task<TResult> object without the need to provide a delegate. Let’s then solve our first case using TaskCompletionSource!
We start refactoring! First, DownloadDeliveryPriceDependingOnWeight() method. First of all, a keyword – async, as we will use a keyword await and a return type, – Task<decimal>, because we will return the taken value. What is more, instead of calling the method of DownloadAsync() product service and attaching an event, we call two newly created methods and await them.
Then, two methods created: DownloadProductsWeightCalculation() and DownloadDeliveryPriceDependingOnWeight(), will be slightly different, but they use the same mechanism. I will describe the first method only, as the differences are explained in the comments of the second method.
We create tcs object of TaskCompletionSource<ProductsServiceResult> type. This object will be a producer of our Task that returns ProductsServiceResult. We create EventHandler<ProductsServiceResult> that will be assigned to productsService.DownloadCompleted. We do not create a new method, as we want to avoid the transfer of tcs, which may be problematic. In the body of downloadCompleted lambda expression, at the beginning, we detach our EventHandler, therefore we get a one piece less! Moreover, we call TrySetResult() method that changes the status of our Task to Completed and returns the taken value.
This is all we need to simplify the potentially complicated code. There is one more thing we get using this pattern. At the place where we call for downloading, we’re able to use the await keyword and track the time of download completion (this may be important when we want to build a larger business process, for example).
What about the second problem presented above? I will not present how ugly and almost impossible to maintain such a solution would be and I will move on to a possible solution. I will not go into details here either, as the implementation of each service would look very similar to the previous example. I only recommend to pay attention to GetVeryImportantObject(). We did not wait for the results one by one, but we only used the aggregation of Tasks and we will not do anything until all operations are completed – this is very important as we want to return the whole object at one moment.
And that’s all 🙂