asynchronous programming

Asynchronous Programming with C#

Reading Time: 6 minutes

A asynchronous programming allows you to run multiple sections of code in parallel.  It can greatly increase the performance for complex calls and it’s been all the rage for RESTful web-api calls in .net for some time.  If you’ve done any web development they you’ve probably seen or used the async/await keyword combination.  This post will is primarily focused on a client up running long and continuous processes, however the concepts can easily be used on RESTful web-api’s as well.

Here’s a quick but easy example to follow: Suppose you are writing an application that is in charge of monitoring the prices of bitcoin.  If a certain threshold is hit, you may want to buy or sell.  In addition to that your application is also monitoring several exchanges (where you can buy and sell on each exchange).  Under these conditions, you want to:

  1. Monitor several exchanges at the same time.
  2. Initiate a buy or sell order without disrupting (stopping) the monitoring of exchanges.

You application needs to run there processes in parallel so they aren’t required to wait for the other to complete.

Monitoring several exchanges at the same time

Suppose we had our exchanges in a bunch of POCO’s (plain old C# object).  Each object needed to go out to the web and get some real-time data.  I’m going to keep the actual functionality to a minimum, the main point here, is to show you a high level of the multi tasking operations.  Enter the Async / Await keywords

What does the async keyword do in C#?

In order to make a method asynchronous you must use the async modifier.  This tells the compiler that we’re going to make the behavior of this function special.  However in order for it actually work as an asynchronous call, we’ll need to use the await keyword within the function call.

What does the await keyword do in C#?

The await keyword is an operator tells the section of code to wait for the method to complete it’s task before execution can move forward.  This can often cause some confusion.  I’ve seen many examples where the await keyword was applied when the entire section could have executed multiple tasks asynchronously but instead the end result was synchronous because we ‘waited’ for each section of code to complete before moving on.  Let’s take a look at this section of code:

Example where each asynchronous task actually runs synchronously

// get the good an bad orders
var gdaxPrices = exhanges.GDax.GetPrice("bitcoin");
await gdaxPrices;

var krakenPrices = exhanges.Kraken.GetPrice("bitcoin");
await krakenPrices;

// do something against each before we can exit

Each “await“, stops the flow of execution, which means the code above is actually creating a blocking call between each ‘async’ call.   We’re losing the performance we’d gain by doing then asynchronously.

I think the original confusion comes from Microsoft’s own explanation of of async/await which statest

An async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method

But that only relates to calling components, the flow of execution with-in the call itself is stopped.  I think they are technically correct here but it also seems to be a bit ambiguous.

For example say the first call to GetPrice() takes 1 second (hopefully this would be less but we’re at the mercy of the exchange) and GetPrice() takes 1.5 seconds, we now have to wait 2.5 seconds to get all the data.  When we await, we are actually ‘waiting’ for this task to complete before we can move on to the next one (within our own method calls).  To the outside world (the callers or our function), control is returned to them, so that they aren’t being blocked.

Back to the flow of our method:  Image, if we are calling 10 or 15 exchanges with this logic.  This means we’re looking at (number_of_exchanges *  call_time,) = total_wait_time,  which could be 15-20 seconds or more as we start adding exchanges.

However if the GetPrice(…) calls are run at the same time, then it will only take as long as the longest result set to complete both (in this example a total time 1.5 seconds) to get everything.  If we start adding other exchanges, then we’re still only waiting as long as it takes for the longest call.

By modifying the code slightly we can get a 1 second performance boost.  Simply do this

var gdaxPrices = exhanges.GDax.GetPrice("bitcoin");
//await gdaxPrices; <- commented out for example - should be completely removed
var krakenPrices = exhanges.Kraken.GetPrice("bitcoin");
//await krakenPrices; <- commented out for example - should be completely removed


// simply wait for both tasks to be completed,
await Task.WhenAll(gdaxPrices, krakenPrices);

// once we get here we can do what ever we want with the prices

Now image if we create a collection of exchanges.  Each exchange will have a Monitor() method, which contains all the internal logic it would need to monitor it’s own exchange and create buy or sell orders.  Once we start the exchange monitoring process they will run independently.  Each can monitor the exchange and whatever crypto currency they want.

Keep in mind this is just a simple example but you’d probably start with something like this:

  • A base abstract class for your Exchange Monitor.
  • Sub Classes for each type of exchange, which would override any base class functionality needed.
  • EventHandlers if needed
  • Your main app to control the flow.

Base Class

public abstract class Exchange
    {
        private CancellationToken _token;
        private CancellationTokenSource _tokenSource;
        //private Task _task = null;

        // a way to send our messages back to someone
        public event EventHandler<ExchangeEventArgs> ExchangeEvent;

        public Exchange(string name)
        {
            Name = name;
        }

        public string Name { get; } = String.Empty;

        public void Start()
        {
            // set up a new token so we can cancel
            if (_tokenSource == null)
            {
                _tokenSource = new CancellationTokenSource();
                _token = _tokenSource.Token;

                // staring the monitor is completed, which will be on an endless loop until it's stopped
                var m = MonitorAsync();
            }
        }
        

        private async Task MonitorAsync()
        {
            // hit the exchange and check some values
            SendMessage($"Getting Latest Price from {Name}");

            // generate some random time it would take to make the call
            Random rnd = new Random();
            int delay = rnd.Next(1, 5);

            await Task.Delay(delay * 1000);

            SendMessage($"Price from {Name} took {delay} seconds");

            // this is just to simulate buying/selling/holding
            // in the real world, you'd want to track if you already have money out there, should you buy more
            // and of course you can't sell unless you have coin(s) already unless you are doing futures
            int buySellHold = rnd.Next(1, 3);

            switch(buySellHold)
            {
                case 1:
                    SendMessage($"You should buy from {Name}");
                    // kick off an async task to buy
                    break;
                case 2:
                    SendMessage($"You should sell from {Name}");
                    // kick off an async task to sell
                    break;
                case 3:
                    SendMessage($"You should hold from {Name}");                    
                    break;
            }
                

            if (_token.IsCancellationRequested)
            {
                SendMessage($"Stopping {Name}");

                // do any clean up here

                return;
            }
            else
            {
                // keep going
                await MonitorAsync();
            }
        }

        /// <summary>
        /// Stop the monitoring
        /// </summary>
        public void Stop()
        {
            this._tokenSource?.Cancel();
        }

       
        private void SendMessage(string message)
        {
            message += " " + DateTime.Now.ToString();
            // write it out in the output window
            Console.WriteLine(message);


            var e = new ExchangeEventArgs() { Message = message };

            // send it up the chain to anyone listening
            ExchangeEvent?.SafeInvoke<ExchangeEventArgs>(this, e);


        }

GDAX Exchange

public class GDAX: Exchange
{
    public GDAX() : base("GDAX") { }        
}

Kraken Exchange

public class Kraken: Exchange
{
    public Kraken() : base("Kraken") { }
}

Event Extensions

public static class ExchangeEventExtensions
{
    public static void SafeInvoke<T>(this EventHandler<T> evt, object sender, T e)
    {
        evt?.Invoke(sender, e);
    }
}

Event Args

public class ExchangeEventArgs
{
    public string Message { get; set; }
}

Main App to Run it

public partial class frmAsyncSamples : Form
    {
        // a place to store our objects in a list
        private List<Exchanges.Exchange> Exchanges = null;
       
        

        public frmAsyncSamples()
        {
            InitializeComponent();
        }

        private void frmAsyncSamples_Load(object sender, EventArgs e)
        {
            // set up the running state
            IsRunning(false);

        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            // lock the button
            IsRunning(true);

            if (Exchanges == null)
            {
                // create our list
                Exchanges = new List<Exchanges.Exchange>();

                // create each object, there's better ways to do this dynamically with a factory but this is good for now
                var gdax = new Exchanges.GDAX();
                var kraken = new Exchanges.Kraken();

                // wire up the events
                gdax.ExchangeEvent += Exchanges_ExchangeEvent;
                kraken.ExchangeEvent += Exchanges_ExchangeEvent;

                // send individual events - just so them separately
                gdax.ExchangeEvent += Gdax_ExchangeEvent;
                kraken.ExchangeEvent += Kraken_ExchangeEvent;

                // add the objects to our in-memory list
                Exchanges.Add(gdax);
                Exchanges.Add(kraken);

                
                foreach (var exchange in Exchanges)
                {
                    txtAllMessages.Text += "Starting: " + exchange.Name + System.Environment.NewLine;
                    // assign to a task just to avoid a warning
                    exchange.Start();

                }


            }

           
        }

        private void Kraken_ExchangeEvent(object sender, Exchanges.ExchangeEventArgs e)
        {
            txtKraken.Text = e.Message + System.Environment.NewLine;
        }

        private void Gdax_ExchangeEvent(object sender, Exchanges.ExchangeEventArgs e)
        {
            txtGDAX.Text = e.Message + System.Environment.NewLine;
        }

        private void Exchanges_ExchangeEvent(object sender, Exchanges.ExchangeEventArgs e)
        {
            txtAllMessages.Text += e.Message + System.Environment.NewLine;
        }

        private void IsRunning(bool isrunning)
        {
            btnStart.Enabled = !isrunning;
            btnStop.Enabled = isrunning;
        }

        private void btnStop_Click(object sender, EventArgs e)
        {

            if (Exchanges != null)
            {
                foreach(var exchange in Exchanges)
                {                    
                    exchange?.Stop();
                }
            }

            IsRunning(false);
        }
    }