C# Proper Task Passing (async await)

Posted in software by Christopher R. Wirz on Mon Dec 28 2015

The await keyword provides a non-blocking way to start a task, then continue execution when that task completes. This is a huge advantage vs blocking a thread, but doesn't really help when you're in a background thread already. What is the best way to await the entire call? Simple, return the whole task and await that. Don't forget, you have to be an an async method. Let's see why this is needed.

Let's try the following code (oh, and good luck trying a rainbow table on my hash - it's salted):


using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace ApiTest
{
    class Program
    {
        /// <summary>
        /// Login to the wirzbrothers server
        /// </summary>
        /// <param name="username_hash">the hash of the username</param>
        /// <param name="password_hash">the hash of the password</param>
        /// <returns>The json results</returns>
        static Task<string> Login(string username_hash, string password_hash)
        {   
            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage()
            {
                Method = HttpMethod.Get,
                RequestUri = new Uri($"https://devapiserver.wirzbrothers.com/api/v1/user/v1/login?wirz_brothers_user_username_hash={username_hash}&wirz_brothers_user_password_hash={password_hash}")
            })
            {
                return client.SendAsync(request).ContinueWith((t) =>
                {
                    return t.Result.Content.ReadAsStringAsync().Result;
                });
            }
        }

        /// <summary>
        /// The main method
        /// </summary>
        /// <param name="args">Arguments not used for example</param>
        static void Main(string[] args)
        {
            var str = Login("ffa2373a1611e892d754f60e9cd06527", "6f4e7970799dd1ee06e6ab9ca9862e89").Result;
            Console.WriteLine(str);
            
            Console.WriteLine("");
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

The expected results are


{
    "zulutime": "2015-02-27T04:03:15Z",
    "user": {
        "wirz_brothers_user_id": "christop-wirz-rejf-ilqv-3eikpkr0x0uc",
        "wirz_brothers_user_first_name": "Christopher",
        "wirz_brothers_user_last_name": "Wirz",
        "wirz_brothers_user_display_name": "Christopher R. Wirz",
        "wirz_brothers_user_display_image": "https:\/\/s3.amazonaws.com\/public-images\/1c8b3cbec3bd3a81bd8100af9c1da5e60424b3786ca07e3816f7d729443ef9bfec3cfa0afe7395ed846431a940cb9da907673e57770c1d8185459ad796dce4e5.png",
        "wirz_brothers_user_username": "test",
        "wirz_brothers_email_address": "test@wbgmail.com",
        "wirz_brothers_auth_token_id": "51kgq0q2-5p77-sp2t-xk7d-808bzrr9m9ch",
        "wirz_brothers_auth_token_expires": "2015-02-27 05:03:15",
        "wirz_brothers_auth_token_field": "x-auth-token",
        "wirz_brothers_auth_token_value": "86758ies-0rgl-gv0u-m2zj-6b01wb7lk7ug",
        "is_admin": true,
        "x-auth-token": "86758ies-0rgl-gv0u-m2zj-6b01wb7lk7ug"
    },
    "message": "test has been logged in",
    "success": true
}

but sadly, but couldn't be further from teh truth. Instead we get the error message System.AggregateException: 'One or more errors occurred. (A task was canceled.)'.

Oh no! What happend?

Give up yet? Well, the client gets disposed when it loses scope. The SendAsync method kicks off and the rest of the method keeps on going until the end of the method scope. To fix this, we can make the entire method an expression, such that everything is in the scope of the caller.


using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace ApiTest
{
    class Program
    {
        /// <summary>
        /// Login to the wirzbrothers server
        /// </summary>
        /// <param name="username_hash">the hash of the username</param>
        /// <param name="password_hash">the hash of the password</param>
        /// <returns>The json results</returns>
        static Task<string> Login(string username_hash, string password_hash)
        =>  new HttpClient().SendAsync(new HttpRequestMessage()
            {
                Method = HttpMethod.Get,
                RequestUri = new Uri($"https://devapiserver.wirzbrothers.com/api/v1/user/v1/login?wirz_brothers_user_username_hash={username_hash}&wirz_brothers_user_password_hash={password_hash}")
            }).ContinueWith((t) =>
            {
                return t.Result.Content.ReadAsStringAsync().Result;
            });

        /// <summary>
        /// The main method
        /// </summary>
        /// <param name="args">Arguments not used for example</param>
        static void Main(string[] args)
        {
            var str = Login("ffa2373a1611e892d754f60e9cd06527", "6f4e7970799dd1ee06e6ab9ca9862e89").Result;
            Console.WriteLine(str);
            
            Console.WriteLine("");
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

Success! That worked. But some would argue that makes debugging a lot harder. Heck, what if I typed the URL wrong? Developers need breakpoints. Let's try async await...


using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace ApiTest
{
    class Program
    {
        /// <summary>
        /// Login to the wirzbrothers server
        /// </summary>
        /// <param name="username_hash">the hash of the username</param>
        /// <param name="password_hash">the hash of the password</param>
        /// <returns>The json results</returns>
        static async Task<string> Login(string username_hash, string password_hash)
        {   
            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage()
            {
                Method = HttpMethod.Get,
                RequestUri = new Uri($"https://devapiserver.wirzbrothers.com/api/v1/user/v1/login?wirz_brothers_user_username_hash={username_hash}&wirz_brothers_user_password_hash={password_hash}")
            })
            {
                return await client.SendAsync(request).ContinueWith((t) =>
                {
                    return t.Result.Content.ReadAsStringAsync().Result;
                });
            }
        }

        /// <summary>
        /// The main method
        /// </summary>
        /// <param name="args">Arguments not used for example</param>
        static void Main(string[] args)
        {
            var str = Login("ffa2373a1611e892d754f60e9cd06527", "6f4e7970799dd1ee06e6ab9ca9862e89").Result;
            Console.WriteLine(str);
            
            Console.WriteLine("");
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

Success! That worked again and as a side beneifit I now know how to regression test my API!