8 - Awaiting an Asynchronous Operation

An Asynchronous Operation

Introduction

Asynchronous programming is the ability to create an application with a task (or tasks) that doesn't (or don't) interfere with another task (or with each other). Both the .NET Framework and the C# language fully support asynchronous operations through various classes and keywords.

When creating synchronous operations, we saw how important it is to think about threads (and processes) competing for the same resources. An example is about two sources (such as two people) trying to access the same bank account, on purpose or by mistake, at the same time. Another example is when a certain operation needs to use many resources at the same time. One more example is for an operation that must take a long time to complete. In these and other situations, you as a programmer should write code that performs operations that do not interfere with other operations. Such operations are referred to as asynchronous operations. The code that takes care of this can be written for a function or method, a lambda expression, or in exception handling.

Demanding Asynchrony

To let you create a function that addresses concerns for one or more asynchronous operations, the C# language provides the async keyword. To start the function, precede its return type with a keyword named async. The formula to follow is:

async data-type method-name(parameter(s))
{
}

If you are creating a method, especially if it will be accessed outside the class, you should start the method with an access keyword, which is usually public or internal (if the method will not be accessed outside the class, you can omit its access level or use private).

The primary job of an asynchronous function is to avoid interferring with other operations. We saw that, to make this possible, you can create a function or method that returns a task. Such a function can (must) return a Task<> object. If the operation must produce a regular value (a number, a character, or a Boolean value), specify the desired type between < and >. In this case, pass the data type as the parameter type. Here is an example:

namespace AsynchronousOperation
{
    public class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }
        
        int SetTicketAmount()
        {
            return 0;
        }

        Task<int> CreateTicket()
        {
            Task<int> ticket = new Task<int>(SetTicketAmount);

            return ticket;
        }

        async Task<int> Command()
        {
        }
    }

}

The function or method may or may not use parameters, it is up to you. Other than that, if you are not planning to use the return value of the method, you can the type of value of the parameter type. Here are examples:

record Synchronizer
{
    async Task<bool> Booleanize()
    {
        return false;
    }

    async Task<char> Characterize()
    {
        return '';
    }

    async Task<int> Setup()
    {
        return 0;
    }

    async Task<double> Numerize()
    {
        return 0.00;
    }
}

This means that you can make the function or method return a valid value as long as that value is appropriate based on the parameter type.

Awaiting a Task

In the function or method that anticipates an asynchronous operation, which is the async function, you must indicate where the long operation is performed. This is done by calling the method that performs the task. We already know how to create such a function. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        int SetTicketAmount()
        {
            return 0;
        }

        Task<int> CreateTicket()
        {
            Task<int> ticket = new Task<int>(SetTicketAmount);

            return ticket;
        }

        async Task<int> Setup()
        {
            return 0;
        }
    }
}

To let you indicate the method that performs the arduous operation, the C# language provides a keyword named await. When calling the method that performs the long operation, precede it with that keyword. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        int SetTicketAmount()
        {
            return 0;
        }

        Task<int> CreateTicket()
        {
            Task<int> ticket = new Task<int>(SetTicketAmount);

            return ticket;
        }

        async Task<int> Setup()
        {
            await CreateTicket();

            return 0;
        }
    }
}

Most of the time, you will need to get the return value of the called function or method and use that value or object as you see fit. To get that value, you can declare a local variable and initialize it with the await calling method. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        int SetTicketAmount()
        {
            return 0;
        }

        Task<int> CreateTicket()
        {
            Task<int> ticket = new Task<int>(SetTicketAmount);

            return ticket;
        }

        async Task<int> Setup()
        {
            int result = await CreateTicket();

            return 0;
        }
    }
}

You can also declare the variable using the var; that's usually the most common or desired technique. After delaring the variable, you can use it as you see fit. For example, if necessary, you can use the returned value when initializing a variable. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        int number;

        public Exercise() => InitializeComponent();

        int SetTicketAmount()
        {
            return 100_000;
        }

        Task<int> CreateTicket()
        {
            Task<int> ticket = new Task<int>(SetTicketAmount);

            return ticket;
        }

        async Task<int> Setup()
        {
            int cost = await CreateTicket();

            TrafficViolation tv = new TrafficViolation()
            {
                Amount = cost,
                Paid = false
            };

            return 0;
        }
    }

    public record struct TrafficViolation
    {
        public int Amount;
        public bool Paid;
    }
}

As always, if you are not planning to use the value many times, you don't have to first declare a variable. You can call the method where it is needed. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        int SetTicketAmount()
        {
            return 100_000;
        }

        Task<int> CreateTicket()
        {
            Task<int> ticket = new Task<int>(SetTicketAmount);

            return ticket;
        }

        async Task<int> Setup()
        {
            TrafficViolation tv = new TrafficViolation()
            {
                Amount = await CreateTicket(),
                Paid = false
            };

            return 0;
        }
    }

    public record struct TrafficViolation
    {
        public int Amount;
        public bool Paid;
    }
}

Awaiting an Asynchronous Object

We already know that the parameter type of a task can be of a class (or structure) type. Based on that, you can create an asynchronous operation whose type is a class or structure. You can use your own class. Here is an example:

class TrafficTicket
{
}
    
public class Exercise
{
    int SetTicketAmount()
    {
        return 0;
    }

    Task<int> CreateTicket()
    {
        Task<int> ticket = new Task<int>(SetTicketAmount);

        return ticket;
    }

    async Task<TrafficTicket> Report()
    {
    }
}

Remember that the .NET Framework provides an impressive collection of classes. You can use one of those classes as a parameter type.

When you are using a class as a parameter type, you can make the function return null. Here are examples:

public class TrafficTicket
{
}

public class Atomicity
{
    async Task<TrafficTicket> Prepare()
    {
        return null;
    }

    async Task<string> Report()
    {
        return null;
    }

    async Task<BinaryWriter> Write()
    {
        return null;
    }

    async Task<Graphics> Draw()
    {
        return null;
    }
}

Or you can return a valid object depending on the type of class. Here are examples:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        async Task<TrafficTicket> Prepare()
        {
            TrafficTicket ticket = new TrafficTicket()
            {
                Amount = 125,
                Paid = true
            };

            // Return the object
            return ticket;
        }

        async Task<string> Report()
        {
            return "The driver drove through a red light";
        }

        async Task<BinaryWriter> Write()
        {
            BinaryWriter bw = new BinaryWriter(new FileStream("tickets.tks",
                                                              FileMode.OpenOrCreate,
                                                              FileAccess.ReadWrite,
                                                              FileShare.ReadWrite));
            return bw;
        }

        async Task<Graphics> Draw()
        {
            return CreateGraphics();
        }
    }

    public record struct TrafficTicket
    {
        public int  Amount { get; set; }
        public bool Paid   { get; set; }
    }
}

As you may know already, the purpose of asynchrony is to let the a function wait for an ongoing operation to first end, in which case you should first create the function or method for that operation. That function must return a task that produces an object of the class of the parameter type. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        TrafficTicket Register()
        {
            return new TrafficTicket()
            {
                Amount = 125,
                Paid   = false
            };
        }

        Task<TrafficTicket> CreateTicket()
        {
            Task<TrafficTicket> infraction = new Task<TrafficTicket>(Register);

            return infraction;
        }

        async Task<TrafficTicket> Prepare()
        {
            return null;
        }
    }

    public record struct TrafficTicket
    {
        public int Amount { get; set; }
        public bool Paid { get; set; }
    }
}

As seen previously, in the async function, you can use the await keyword to indicate the operation that must first complete. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        TrafficTicket Register()
        {
            return new TrafficTicket()
            {
                Amount = 125,
                Paid   = false
            };
        }

        Task<TrafficTicket> CreateTicket()
        {
            Task<TrafficTicket> infraction = new Task<TrafficTicket>(Register);

            return infraction;
        }

        async Task<TrafficTicket> Prepare()
        {
            await CreateTicket();

            return null;
        }
    }

    public record structrecord struct TrafficTicket
    {
        public int Amount { get; set; }
        public bool Paid { get; set; }
    }
}

Other Tasks types

A Task that Produces a Tuple

A tuple is a combination of values. You can create a task thaproduces a tuple. This gives you the advantage of a task that produces many values instead of a simple one. Everything else is done as for a value type. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        (string, int, bool) Register()
        {
            int    amount   = 75;
            bool   complete = false;
            string where    = "Paloma Drive and Derrick Street";

            return (where, amount, complete);
        }

        Task<(string, int, bool)> CreateTicket()
        {
            Task<(string, int, bool)> infraction = new Task<(string, int, bool)>(Register);

            return infraction;
        }

        async Task<(string, int, bool)> Prepare()
        {
            (string location, int amount, bool paid) consideration = await CreateTicket();

            StringBuilder first = new StringBuilder("The traffic violation occurred on ");
            StringBuilder second = new StringBuilder(consideration.location);
            StringBuilder third = new StringBuilder(". There driver was issued a ticket in the amount of ");
            StringBuilder fourth = new StringBuilder(consideration.amount.ToString("C"));
            StringBuilder fifth;

            if (consideration.paid == false)
                fifth = new StringBuilder("The driver has not yet paid the violation.");
            else
                fifth = new StringBuilder("The payment has already been made for the ticket.");

            string strSummary = first.ToString() + second.ToString() + 
                                third.ToString() + fourth.ToString() + fifth.ToString();

            return consideration;
        }
    }
}

A Task that Produces a Structure

A structure is a type of value that is originally created like a class. You can create a task that produces a value of a structure type. When doing this, specify the parameter type as a structure. You can use your own structure. A structure is a value type as opposed to a refereence type. Therefore, by default, a structure cannot hold a null:

public struct Speed
{
    public int Limit   { get; set; }
    public int Driving { get; set; }
}

public class Acceleration
{
    async Task<Speed> Categorize()
    {
        // The following line produces an error
        return null;
    }
}

As a solution, if you create an asynchronous function but you don't have a value to return from it, you can make it return some type of default value. Here is an example:

public struct Speed
{
    public int Limit   { get; set; }
    public int Driving { get; set; }
}

public class Acceleration
{
    async Task<Speed> Categorize()
    {
        return new Speed() { Limit = 0, Driving = 0 };
    }
}

Other than that, you can follow the same steps we used for a class. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        Speed Register()
        {
            Speed evaluation = new Speed() { Limit = 45, Driving = 86 };
            
            return evaluation;
        }

        Task<Speed> CreateTicket()
        {
            Task<Speed> infraction = new Task<Speed>(Register);

            return infraction;
        }

        async Task<Speed> Transmit()
        {
            Speed spd = await CreateTicket();

            return spd;
        }
    }

    public struct Speed
    {
        public int Limit { get; set; }
        public int Driving { get; set; }
    }
}

Remember that, in C#, to ask the compiler to consider the nullity of a value type, you can add a question mark to the type. That way, you can make a sync function or method return null. Here are examples:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        int ? SetTicketAmount()
        {
            return 125;
        }

        Task<int?> RegisterTicket()
        {
            Task<int?> ticket = new Task<int?>(SetTicketAmount);

            return ticket;
        }

        async Task<int?> Setup()
        {
            int ? cost = await RegisterTicket();

            return null;
        }

        Speed ? Register()
        {
            Speed ? evaluation = new Speed() { Limit = 45, Driving = 86 };
            
            return evaluation;
        }

        Task<Speed?> CreateTicket()
        {
            Task<Speed?> infraction = new Task<Speed?>(Register);

            return infraction;
        }

        async Task<Speed?> Transmit()
        {
            Speed ? spd = await CreateTicket();

            return null;
        }
    }

    public struct Speed
    {
        public int Limit { get; set; }
        public int Driving { get; set; }
    }
}

A Task for an Interface

You can create a task that produces an interface. The advantage is that this allows you to deal with various classes, those that implement that interface. Of course, you must first have an interface. You can create your own interface, then use it as the parameter type of a task. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        IPublication GetPublication()
        {
            IPublication pub = new Book()
            {
            };

            return pub;
        }

        Task<IPublication> Setup()
        {
            Task<IPublication> pub = new Task<IPublication>(GetPublication);

            return pub;
        }
    }
    
    public interface IPublication
    {
    }

    public class Book : IPublication
    {
    }
}

When creating an asynchronous function or method, specify its parameter type as the interface. If you don't have an object to return, return null. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        IPublication GetPublication()
        {
            IPublication pub = new Book() { };

            return pub;
        }

        Task<IPublication> Setup()
        {
            Task<IPublication> pub = new Task<IPublication>(GetPublication);

            return pub;
        }

        async Task<IPublication> Publish()
        {
            return null;
        }
    }
    
    interface IPublication { }
    class Book : IPublication { }
}

Othewise, in the body of the asynchronous function or method, you need to indicate the operation that must be completed, which is done by using the await keyword followed by the name of the function that oerforms the operation. Here is an example:

namespace Asynchronicity
{
    public partial class Exercise : Form
    {
        public Exercise() => InitializeComponent();

        IPublication GetPublication()
        {
            IPublication pub = new Book()
            {
                ItemNumber = 293_849,
                Title = "One by One Hardcover",
                Author = "Ruth Ware",
                Format = Format.Paperback,
                Price = 27.85
            };

            return pub;
        }

        Task<IPublication> Setup()
        {
            Task<IPublication> pub = new Task<IPublication>(() =>
            {
                return new Book()
                {
                    ItemNumber = 293_849,
                    Title = "One by One Hardcover",
                    Author = "Ruth Ware",
                    Format = Format.Paperback,
                    Price = 27.85
                };
            });

            return pub;
        }

        async Task<IPublication> Publish()
        {
            IPublication engage = await Setup();

            return engage;
        }
    }

    public enum Format { Paperback, Hardcover, Electronic, AudioRegular, AudioDramatized, Other }

    interface IPublication
    {
        int ItemNumber { get; set; }
        string Title { get; set; }
        string Author { get; set; }
        Format Format { get; set; }
        double Price { get; set; }
    }
    
    class Book : IPublication
    {
        public int ItemNumber { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public Format Format { get; set; }
        public double Price { get; set; }
    }
}

Previous Copyright © 2014-2024, FunctionX Monday 05 July 2024, 11:36 Home