Introduction
Let’s say there are two functions, Foo1
and Foo2
. Foo2
needs
to be called after Foo1
, and only Foo1
can call Foo2
:
A realistic scenario might be when Foo1
is invoked using a
web service. Foo1
is called and needs to return as quickly as possible so that
the calling client doesn’t hang. This means both Foo1
and Foo2
must be asynchronous
functions, and that Foo2
must run after Foo1
has returned.
For Foo1
to be able to call Foo2
after Foo1
returns can be
done in multiple ways. One way might be to have external queue into which tasks
can be inserted so that they can be run when a certain state is set by a
separate thread. This is good for scalable enterprise architecture, but seems a
bit of an overkill when all we want to do is to call Foo2
—after Foo1
.
Another way is using the timer object. A third way is using
the relatively new ‘async’ method modifier.
The “async” function modifier was introduced in Visual Studio
2012/2013.
Before talking more about the async modifier, here is a complete listing of the code that is also attached to this tip. It's a console application:
using System;
namespace MyCSasync
{
using System.Threading;
using System.Threading.Tasks;
class MyClass
{
public static string[] Strings = new string[3];
public static int Count = 0;
private async Task Foo2()
{
await Task.Yield();
Strings[Count++] = "Foo2 called at " + DateTime.Now.Ticks;
}
public async Task Foo1()
{
Strings[Count++] = "Foo1 at " + DateTime.Now.Ticks;
Foo2();
Strings[Count++] = "End of Foo1 at " + DateTime.Now.Ticks;
}
}
class Program
{
static void Main(string[] args)
{
MyClass myclass = new MyClass();
myclass.Foo1();
while (MyClass.Count != 3) Thread.Sleep(1);
foreach (string msg in MyClass.Strings)
{
Console.WriteLine(msg);
}
}
}
}
While Foo1()
is called asynchronously from the Main
function, it could be called from a web service method which needs to return back to a client as quickly as possible so the client doesn't seem to hang. Foo2()
is a private
function that will need to be called after Foo1
completes.
Foo2
is partially executed so that Foo1
can return. This partial execution is just
await Task.Yield()
This allows Foo2
to temporarily exit so that Foo1
can finish. The "Yield
" method, as described by Microsoft, "creates an awaitable task that asynchronously yields back to the current context when awaited." That's exactly what we would like to do. There are other ways to do this. Using a delay. The problem with that approach is the behavior of the code may change depending on how fast your computer is. This is easy to test. If you use Task.Delay(1)
, which is 1 millisecond, it is more than enough to make the code above seem to be behaving the same. If you change the Delay
parameter to a TimeSpan
object with 1 tick, you may see that the code no longer works as expected.
When you run the code above (ctrl-F5), you should see something similar to:
Foo2
is called after Foo1
. Note the tick difference, about 20,000. On my machine, that's about 3200th of a second. So a delay of 1 second would seem to work, but certainly not 1 tick. As an exercise, change the code "await Task.Yield()
" to "await Task.Delay(1)
" and you'll likely see that Foo2
is called after Foo1
. Now change it to "await Task.Delay(TimeSpan.FromTicks(1))
". You will see that Foo2
is called before "End of Foo1
". This is because Foo2
's await
took less time than Foo1
could return.