Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 22, 2021 06:20 am GMT

Asynchronous C: Cherry on the top (Tips and Tricks)

In Part 1 and Part 2 of the series, we took a nice dive to get us started in writing efficient async code in C#, I wanted to finalize the series with a couple of Tips and Tricks that will certainly come in handy in most development cases.

Buckle up your seatbelts ..

Tip #1

static async Task BoilWaterAsync(){    Console.WriteLine("Starting the kettle");    await Task.Delay(3000);    Console.WriteLine("Kettle Finished Boiling");}static async Task PourWaterAsync(){    var boilWaterTask = BoilWaterAsync();    await boilWaterTask;    Console.WriteLine("Pouring boiling water.");}static async Task Main(string[] args){    // Notice we are not awaiting the task!    PourWaterAsync();}

Not awaiting a method call means that no inner tasks would be awaited too, so await boilWaterTask; has no effect, so does await Task.Delay(3000);.

Of course, rewriting these two lines:

var boilWaterTask = BoilWaterAsync();await boilWaterTask;

to be:

await BoilWaterAsync();

.. would give the same result, the task of boiling the water would start running but would not be awaited.

Tip #2

public void Main(string[]args){    var task = BoilWaterAsync();    // DON'T    var result = task.Result;    // A NO-NO    task.Wait();    // PLEASE DON'T     task.GetAwaiter().GetResult();}

The above three DON'Ts will cause the application's main thread to be blocked till the tasks are finished, the app might stop responding or processing new requests till the synchronous wait is completed.

DO:
Embrace the asynchronous nature of the task, await it, and return the result. Tasks being propagated throughout your code is normal, just try to delay doing so as much as you can.

Tip #3

Let's say we are implementing an interface, one of its methods requires a Task to be returned, BUT the execution of the method itself does not require an asynchronous setup, it would not block the main thread and can run synchronously without problems.

interface MyInterface{    Task<string> DoSomethingAndReturnAString();}class MyClass : MyInterface{    public Task<string> DoSomethingAndReturnAString()    {        // Some logic that does not need the await keyword        return "result"; // Of course a compiler error, expecting Task<string> not a string    }}

We can solve this issue with two approaches:

Solution 1 (a bad one ):

Convert the method to be async (even though we do not need to await anything), and now we could return a string normally, right? DON'T ever do that!

The moment you mark a method as async, the compiler transforms your code into a state machine that keeps track of things like yielding execution when an await is reached and resuming execution when a background job has finished. In fact, if you checked the IL (Intermediate Language) generated from your code after marking a method as async, you'll notice that the function has turned into an entire class for that matter.

So even on synchronous method marked with async (without await inside), a state machine will still be generated anyways and the code which could potentially be inlined and executed much faster will be generating additional complexity for state machine management.

That's why it's so important to mark methods as async if and only if there is await inside.

Solution 2 (much better ):

Instead: return Task.FromResult("result");

Let's take another example:

We have an HTTP client retrieving some text from an external web source,

public Task<string> GetFromWebsite(){    var client = new HttpClient();    var content = client.GetStringAsync("my.website.com");    }

If the objective is to only start retrieving the string from the website, then the best way to do it is to return the task of GetStringAsync() as is, and do not await it here, it boils down to the same reason of skipping the aimless creation of a state machine.

public Task<string> GetFromWebsite(){    var client = new HttpClient();    return client.GetStringAsync("my.website.com");}

The only case you'd want to await the call is that you want to perform some logic on the result inside the method:

// Notice that we marked the method as asyncpublic async Task<string> GetFromWebsiteAndValidate(){    var client = new HttpClient();    var result = await client.GetStringAsync("my.website.com");    // Perform some logic on the result    return result;}

Remember, if you used the word "and" while describing what a method does, something might not be right.
Example:

  • "My method is doing x and y and z" (NO-NO )
  • "I have 3 methods, one for doing x, another for y, and the last for z" (YES )

This will make your life much easier when trying to apply unit tests to your code.

So a rule of thumbs to note down here:
Only await when you absolutely need the result of the task.
The async keyword does not run the method on a different thread, or do any other kind of hidden magic, hence, only mark a method as async when you need to use the keyword await in it.

A couple of notes to always remember:

  • An async function can return either on of the three types: void, Task, or Task<T>.
  • A function is "awaitable" because it returns a Task or a Task<T>, not because it is marked async, so we can await a function that is not async (a one just returning Task).
  • You cannot await a function returning void, hence, you should always return a Task unless there is an absolute reason not to do so (a caller to the function expects a void return type), or that the function itself is a top-level function and there is no way other functions will be able to call it..

Tip #4

We established that long-running tasks should always execute asynchronously, these tasks can fall down into two main categories, I/O-Bound and CPU-Bound. A task is said to be I/O bound when the time taken for it to complete is determined principally by the period spent waiting for input/output operations to be completed. In contrast, a CPU-Bound task is a task that's time of completion is determined principally by the speed of the central processor, examples for these would be:

  • I/O Bound Tasks:
    • Requesting data from the network
    • Accessing the database
    • Reading/Writing to a file system
  • CPU Bound Tasks: Generally performing an expensive calculation such as:
    • Graphics Rendering
    • Video Compression
    • Heavy mathematical computations

Generally, whenever you got an I/O Bound Task or Task<T> at hands, await it in an async method. For CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.

Note :
Make sure you analyzed the execution of your CPU-Bound code, and be mindful of the context switching overhead when multithreading, it might be not so costly after all in comparison.

Okay, that's all I have for y'all today.
Keep Coding


Original Link: https://dev.to/paulafahmy/asynchronous-c-cherry-on-the-top-tips-and-tricks-4eod

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To