Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Software Architecture with C# 9 and .NET 5

You're reading from   Software Architecture with C# 9 and .NET 5 Architecting software solutions using microservices, DevOps, and design patterns for Azure

Arrow left icon
Product type Paperback
Published in Dec 2020
Publisher Packt
ISBN-13 9781800566040
Length 700 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Authors (2):
Arrow left icon
Gabriel Baptista Gabriel Baptista
Author Profile Icon Gabriel Baptista
Gabriel Baptista
Francesco Abbruzzese Francesco Abbruzzese
Author Profile Icon Francesco Abbruzzese
Francesco Abbruzzese
Arrow right icon
View More author details
Toc

Table of Contents (26) Chapters Close

Preface 1. Understanding the Importance of Software Architecture 2. Non-Functional Requirements FREE CHAPTER 3. Documenting Requirements with Azure DevOps 4. Deciding the Best Cloud-Based Solution 5. Applying a Microservice Architecture to Your Enterprise Application 6. Azure Service Fabric 7. Azure Kubernetes Service 8. Interacting with Data in C# – Entity Framework Core 9. How to Choose Your Data Storage in the Cloud 10. Working with Azure Functions 11. Design Patterns and .NET 5 Implementation 12. Understanding the Different Domains in Software Solutions 13. Implementing Code Reusability in C# 9 14. Applying Service-Oriented Architectures with .NET Core 15. Presenting ASP.NET Core MVC 16. Blazor WebAssembly 17. Best Practices in Coding C# 9 18. Testing Your Code with Unit Test Cases and TDD 19. Using Tools to Write Better Code 20. Understanding DevOps Principles 21. Challenges of Applying CI Scenarios 22. Automation for Functional Tests 23. Answers 24. Another Book You May Enjoy
25. Index

Performance issues that need to be considered when programming in C#

Nowadays, C# is one of the most commonly used programming languages all over the world, so good tips about C# programming are fundamental for the design of good architectures that satisfy the most common non-functional requirements.

The following sections mention a few simple but effective tips – the associated code samples are available in the GitHub repository of this book.

String concatenation

This is a classic one! A naive concatenation of strings with the + string operator may cause serious performance issues since each time two strings are concatenated, their contents are copied into a new string.

So, if we concatenate, for instance, 10 strings that have an average length of 100, the first operation has a cost of 200, the second one has a cost of 200+100=300, the third one has a cost of 300+100=400, and so on. It is not difficult to convince yourself that the overall cost grows like m*n2, where n is the number of strings and m is their average length. n2 is not too big for small n (say, n < 10), but it becomes quite big when n reaches the magnitude of 100-1,000, and unacceptable for magnitudes of 10,000-100,000.

Let us look at this with some test code, which compares naive concatenation with the same operation that is performed with the help of the StringBuilder class (the code is available in this book's GitHub repository):

Figure 2.11: Concatenation test code result

If you create a StringBuilder class with something like var sb =new System.Text.StringBuilder(), and then you add each string to it with sb.Append(currString), the strings are not copied; instead, their pointers are queued in a list. They are copied in the final string just once, when you call sb.ToString() to get the final result. Accordingly, the cost of StringBuilder-based concatenation grows simply as m*n.

Of course, you will probably never find a piece of software with a function like the preceding one that concatenates 100,000 strings. However, you need to recognize pieces of code similar to these ones where the concatenation of some 20-100 strings, say, in a web server that handles several requests simultaneously, might cause bottlenecks that damage your non-functional requirements for performance.

Exceptions

Always remember that exceptions are much slower than normal code flow! So, the usage of try-catch needs to be concise and essential, otherwise, you will create big performance issues.

The following two samples compare the usage of try-catch and Int32.TryParse to check whether a string can be converted into an integer, as follows:

private static string ParseIntWithTryParse()
{
    string result = string.Empty; 
    if (int.TryParse(result, out var value))
        result = value.ToString();
    else
        result = "There is no int value";
    return $"Final result: {result}";
}
private static string ParseIntWithException()
{
    string result = string.Empty;
    try
    {
        result = Convert.ToInt32(result).ToString();
    }
    catch (Exception)
    {
        result = "There is no int value";
    }
    return $"Final result: {result}";
}

The second function does not look dangerous, but it is thousands of times slower than the first one:

Figure 2.12: Exception test code result

To sum this up, exceptions must be used to deal with exceptional cases that break the normal flow of control, for instance, situations when operations must be aborted for some unexpected reasons, and control must be returned several levels up in the call stack.

Multithreading environments for better results – dos and don'ts

If you want to take advantage of all the hardware that the system you are building provides, you must use multithreading. This way, when a thread is waiting for an operation to complete, the application can leave the CPU to other threads, instead of wasting CPU time.

On the other hand, no matter how hard Microsoft is working to help with this, parallel code is not as simple as eating a piece of cake: it is error-prone and difficult to test and debug. The most important thing to remember as a software architect when you start considering using threads is does your system require them? Non-functional and some functional requirements will answer this question for you.

As soon as you are sure that you need a multithreading system, you should decide on which technology is more adequate. There are a few options here, as follows:

  • Creating an instance of System.Threading.Thread: This is a classic way of creating threads in C#. The entire thread life cycle will be in your hands. This is good when you are sure about what you are going to do, but you need to worry about every single detail of the implementation. The resulting code is hard to conceive and debug/test/maintain. So, to keep development costs acceptable, this approach should be confined to a few fundamental, performance critical modules.
  • Programming using System.Threading.Tasks.Parallel classes: Since .NET Framework 4.0, you can use parallel classes to enable threads in a simpler way. This is good because you do not need to worry about the life cycle of the threads you create, but it will give you less control about what is happening in each thread.
  • Develop using asynchronous programming: This is, for sure, the easiest way to develop multithreaded applications since the compiler takes on most of the work. Depending on the way you call an asynchronous method, you may have the Task created running in parallel with the Thread that was used to call it or even have this Thread waiting without suspending for the task created to conclude. This way, asynchronous code mimics the behavior of classical synchronous code while keeping most of the performance advantages of general parallel programming:
    • The overall behavior is deterministic and does not depend on the time taken by each task to complete, so non-reproducible bugs are more difficult to happen, and the resulting code is easy to test/debug/maintain. Defining a method as an asynchronous task or not is the only choice left to the programmer; everything else is automatically handled by the runtime. The only thing you should be concerned about is which methods should have asynchronous behavior. It is worth mentioning that defining a method as async does not mean it will execute on a separate thread. You may find useful information in a great sample at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/.
    • Later in this book, we will provide some simple examples of asynchronous programming. For more information about asynchronous programming and its related patterns, please check Task-Based Asynchronous Patterns in the Microsoft documentation (https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap).

No matter the option you choose, there are some dos and don'ts that, as a software architect, you must pay attention to. These are as follows:

  • Do use concurrent collections (System.Collections.Concurrent): As soon as you start a multithreading application, you have to use these collections. The reason for this is that your program will probably manage the same list, dictionary, and so on from different threads. The use of concurrent collections is the most convenient option for developing thread-safe programs.
  • Do worry about static variables: It is not possible to say that static variables are prohibited in multithreading development, but you should pay attention to them. Again, multiple threads taking care of the same variable can cause a lot of trouble. If you decorate a static variable with the [ThreadStatic] attribute, each thread will see a different copy of that variable, hence solving the problem of several threads competing on the same value. However, ThreadStatic variables cannot be used for extra-thread communications since values written by a thread cannot be read by other threads. In asynchronous programming, AsyncLocal<T> is the option for doing something like that.
  • Do test system performance after multithreading implementations: Threads give you the ability to take full advantage of your hardware, but in some cases, badly written threads can waste CPU time just doing nothing! Similar situations may result in almost 100% CPU usage and unacceptable system slowdowns. In some cases, the problem can be mitigated or solved by adding a simple Thread.Sleep(1) call in the main loop of some threads to prevent them from wasting too much CPU time, but you need to test this. A use case for this implementation is a Windows Service with many threads running in its background.
  • Do not consider multithreading easy: Multithreading is not as simple as it seems in some syntax implementations. While writing a multithreading application, you should consider things such as the synchronization of the user interface, threading termination, and coordination. In many cases, programs just stop working well due to a bad implementation of multithreading.
  • Do not forget to plan the number of threads your system should have: This is important especially for 32-bit programs. There is a limitation regarding how many threads you can have in any environment. You should consider this when you are designing your system.
  • Do not forget to end your threads: If you do not have the correct termination procedure for each thread, you will probably have trouble with memory and handling leaks.
You have been reading a chapter from
Software Architecture with C# 9 and .NET 5 - Second Edition
Published in: Dec 2020
Publisher: Packt
ISBN-13: 9781800566040
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at ₹800/month. Cancel anytime
Visually different images