Skip to main content

Understanding Asynchronous Programming

Asynchronous programming allows threads or workers to handle multiple tasks concurrently. A Task represents a unit of work, and the await keyword lets you pause execution without blocking the thread—freeing it to handle other operations while waiting for the task to complete.

🍳 Real-Life Analogy: Making Breakfast

Imagine you're preparing breakfast. The tasks might include:

  • Brewing coffee

  • Heating the pan

  • Frying eggs and bacon

  • Toasting bread

  • Spreading jam

  • Pouring juice

If you perform these tasks synchronously, completing one before starting the next, it would take significantly longer. But with an asynchronous approach, you can heat the pan and, while waiting, brew coffee, toast bread, and pour juice. By overlapping tasks, you reduce idle time and improve overall efficiency.

This mirrors how asynchronous code works: it enables your application to stay responsive and efficient by not waiting idly for each operation to finish.

🧪 Using Async-Await Properly

In C#, marking a method with async signals to the compiler that it contains await expressions and asynchronous operations. This allows the method to return control to the caller while the awaited task completes in the background.

Here’s a practical example from Microsoft learn that shows how to implement async/await correctly.

The Inefficient Way: Sequential Awaits

Coffee cup = PourCoffee();

Task<Egg> eggsTask = FryEggsAsync(2);
var eggs = await eggsTask;

Task<HashBrown> hashBrownTask = FryHashBrownsAsync(3);
var hashBrown = await hashBrownTask;

Task<Toast> toastTask = ToastBreadAsync(2);
var toast = await toastTask;

ApplyButter(toast);
ApplyJam(toast);

Juice oj = PourOJ();

In this example, each task is awaited immediately after it's started. That means the program waits for the eggs to finish frying before even starting the hash browns, and so on. This defeats the purpose of asynchronous programming, it’s still sequential, just with prettier syntax.

The Optimized Way

Coffee cup = PourCoffee();

Task<Egg> eggsTask = FryEggsAsync(2);
Task<HashBrown> hashBrownTask = FryHashBrownsAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

var toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);

Juice oj = PourOJ();

var eggs = await eggsTask;
var hashBrown = await hashBrownTask;

Console.WriteLine("Breakfast is ready!");

The optimized way is better because:

  • All cooking tasks (FryEggsAsync, FryHashBrownsAsync, ToastBreadAsync) start at the same time.

  • We only await when we actually need the result.

  • While the toast is toasting, we can pour coffee and juice.

  • Once the toast is ready, we apply butter and jam.

  • Then we await the eggs and hash browns—likely already done or nearly done.

This approach mirrors real-life multitasking and showcases the true power of asynchronous programming: concurrent execution with minimal idle time.



Comments

Popular posts from this blog

Ensuring Data Integrity: The Role of Database Transactions

 1. What is database translation Database transactions are activities transferring, changing data from one consistent state to another consistent state for example in everyday life we make different kind of business transactions from buying products, changing or cancelling orders, buying tickets, etc. And all these activities involve the movement/transaction of data in the database. For example, consider a simple transaction of moving an amount of 5000 from one bank account to another. This transaction includes several steps: decrease the account balance by 5000, and then increase the other account balance by 50003. Each of these steps is a part of the transaction, and if any step fails, the entire transaction fails 2. Why it is crucial in reliable software systems? When it comes to business, whether it is personal or financial data, it is crucial to ensure the reliability and stability of data management. In our daily lives, we may frequently encounter errors related to database t...

Declarative Programming in Angular with Async Pipe and shareReplay

A declarative approach is a way that focuses on writing code that specifies what the application should do, rather than detailing how it should be done. For example, with the async pipe in Angular, we don’t need to write code to manually subscribe to an Observable, handle its data, and update the view. Instead, we simply specify in the template that we want the data from the Observable using the async pipe. Angular handles all the underlying processes to retrieve and display the data It's often used in reactive programming with RxJS and Angular's built-in features, such as the async pipe. export class ProductComponent { product$ = this.productService.getProduct(); constructor(private productService: ProductService) {} } The product observable will hold the product data and the async pipe in the template will automatically subscribe and unsubscribe observable <div *ngIf="product$ | async as product"> <h1>{{ product.name }}</h1> <p>{{...

The Developer’s Guide to Clean Code: Tips and Techniques

What is clean code? Clean code is a term used to describe code that is easy to read, understand, and maintain. It is written in a way that makes it simple, concise, and expressive. Clean code follows a set of conventions, standards, and practices that make it easy to read and follow. Here are some signs indicating that the code is not clean: 1. Poor names The name is not clear to understand, meaningless, or misleading . It doesn't reveal the intention of what it want to achieve. Consider the following examples: SqlDataReader drl; int od; void Button1_Click(); Class Pages1 In the examples above, it’s challenging to get the purpose of drl, od, or what Button1_Click() does. To enhance clarity, we can rename these identifiers as follows: SqlDataReader dataReader/reader; int overdueDays; void CheckAvailability_Click(); Class ViewCustomerPage {} Ambiguous names int? incidentNameId for instance. incidentNameId lacks clarity because if it represents the ID of an incident, then the inclu...