A Tiny history lesson, before c# 4.0 we had to use Threads and the ThreadPool for asynchronous programming; this was hard. Now we have the Task Parallel library which made life much easier, after that the magical async/await keywords appeared. I say magical, because that's exactly what they are they're just syntactical fairy dust that actually turns into the task.run().ContinueWith.
Lets start with a console application, take a look at the following:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Example00
{
class Program
{
static void Main(string[] args)
{
var sw = new Stopwatch();
sw.Start();
var num0 = GetNumber(3);
var num1 = GetNumber(4);
sw.Stop();
Console.WriteLine("{0} + {1} =
{2} in {3}'s", num0, num1, num0 + num1,
sw.Elapsed.Seconds);
Console.WriteLine("Good
Times");
}
public static int GetNumber(int num)
{
Task.Delay(1000 * num).Wait();
return num;
}
}
}
that's exactly what we expected but did we really need to wait for our calculation to complete before seeing the "Good Times" Message, it really has nothing to do with the calculation.
Let's use the Task Parallel library to do better:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Example01
{
class Program
{
static void Main(string[] args)
{
var num0 = 3;
var num1 = 4;
var sw = new Stopwatch();
sw.Start();
Task T = Task.Run(() =>
{
Console.WriteLine("Sum
TaskId:{0}", Task.CurrentId);
return GetNumber(num0) + GetNumber(num1);
}).ContinueWith(tr =>
{
sw.Stop();
Console.WriteLine("\nContinueWith
TaskId:{0} ", Task.CurrentId);
Console.WriteLine("{0} + {1} =
{2} in {3}'s", num0, num1, tr.Result,
sw.Elapsed.Seconds);
});
Console.WriteLine("Good
Times");
Task.WaitAll(T);
}
public static int GetNumber(int num)
{
Console.WriteLine("GetNumber({1})
TaskId:{0} ", Task.CurrentId, num);
Task.Delay(1000 * num).Wait();
return num;
}
}
}
but as you can see we only have two tasks in this modification, one to get our numbers synchronously and add them up, then a second task to display our results.
We can do better lets start up two more Tasks inside of our sum task to get the numbers separately and shave some time off our calculation time.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Example02
{
class Program
{
static void Main(string[] args)
{
var num0 = 3;
var num1 = 4;
var sw = new Stopwatch();
sw.Start();
Task T = Task.Run(() =>
{
Console.WriteLine("Sum
TaskId:{0}", Task.CurrentId);
var t0 = Task<int>.Run(() => GetNumber(num0));
var t1 = Task<int>.Run(() => GetNumber(num1));
return t0.Result + t1.Result;
}).ContinueWith(tr =>
{
sw.Stop();
Console.WriteLine("\nContinueWith
TaskId:{0} ", Task.CurrentId);
Console.WriteLine("{0} + {1} =
{2} in {3}'s", num0, num1, tr.Result,
sw.Elapsed.Seconds);
});
Console.WriteLine("Good
Times");
Task.WaitAll(T);
}
public static int GetNumber(int num)
{
Console.WriteLine("GetNumber({1})
TaskId:{0} ", Task.CurrentId, num);
Task.Delay(1000 * num).Wait();
return num;
}
}
}
now lets try the same thing but using the async/await keywords.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Example03
{
class Program
{
static void Main(string[] args)
{
Action<int,int> calculate = async (num0, num1) => {
var sw = new Stopwatch();
sw.Start();
int total = await Task.Run<int>(() => {
Console.WriteLine("Sum TaskId:{0}", Task.CurrentId);
var t0 = Task<int>.Run(() =>
GetNumber(num0));
var t1 = Task<int>.Run(() =>
GetNumber(num1));
return t0.Result +
t1.Result;
});
sw.Stop();
Console.WriteLine("{0} + {1} =
{2} in {3}'s", num0, num1, total,
sw.Elapsed.Seconds);
};
calculate(3, 4);
Console.WriteLine("Good
Times");
Console.ReadKey();
}
public static int GetNumber(int num)
{
Console.WriteLine("GetNumber({1})
TaskId:{0} ", Task.CurrentId, num);
Task.Delay(1000 * num).Wait();
return num;
}
}
}
the above being a bit convoluted, here's a final example to really demonstrate that Task.Run().ContinueWith is the same thing as async/await
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Example04
{
class Program
{
static void Main(string[] args)
{
var num0 = 3;
var num1 = 4;
var sw = new Stopwatch();
sw.Start();
var result0 = SlowAdd(num0, num1);
sw.Stop();
Console.WriteLine("TPL: {0} +
{1} = {2} in {3}'s", num0, num1, result0,
sw.Elapsed.Seconds);
sw.Restart();
var result1 = SlowAddAsync(num0, num1).Result;
sw.Stop();
Console.WriteLine("ASYNC: {0} +
{1} = {2} in {3}'s", num0, num1, result1,
sw.Elapsed.Seconds);
}
static int SlowAdd(int a, int b)
{
return GetNumber(3) + GetNumber(4);
}
async static Task<int> SlowAddAsync(int a, int b)
{
return await GetNumberAsync(a) + await GetNumberAsync(b);
}
static int GetNumber(int num)
{
//start
a task
return Task.Run(
//wait
the number * seconds
() => Task.Delay(1000 * num))
//once
waiting task complete continue
.ContinueWith(
//return
the number
tr => num).Result;
}
async static Task<int> GetNumberAsync(int num)
{
await Task.Delay(1000 * num);
return num;
}
}
}
above, the two functions to pay attention to are GetNumber and GetNumberAsync, they both do the same thing, they take in a number wait an equal amount of seconds then pass the number back. In fact they will compile down to roughly the same code it's just that one is much easier to read than the other.