Asynchronous programming model in .NET makes it very easy
for developers to write event based asynchronous pattern (EAP) code starting
.NET version 4.5. We have been trained for long enough by .NET programming
practices to use event based pattern for any asynchronous work. For example,
you want to perform a long running operation. When calling this long running
function, you don’t want your client code to make a blocking call; instead a
very famous pattern that many architects suggests here is to ask you client to
pass a callback function or delegate and that callback function will be called as
soon as this long running operation is over. This is nothing but the EAP
pattern.
As shown in following example, function “client” makes a
call to “longfunction” and waits for the long function to complete. This is a
blocking call.
private Task<string> CallLongRunningService()
{
var client = new ServiceReference1.Service1Client();
// blocking call where main thread is blocked
client.CallLongRunningService();
}
Now instead of just waiting for “longfunction” to complete, “client”
function wants to do some other stuff. Here many architects would suggest to
use EAP pattern and call the long function asynchronously.
So we refactor “CallLongRunningService”
to “CallLongRunningServiceWithCallback”
and ask a callback function to be called when long function is complete.
private void CallLongRunningServiceWithCallback()
{
var client = new ServiceReference1.Service1Client();
client.LongFunctionCallback
+= LongFunctionCallback;
client.RunLongFunctionAsync();
}
private void LongFunctionCallback(CallbackArgs
args)
{
// set the result in some local variable. Basically store the result so
that others
// can access it
m_result = args.Result;
}
As you can guess “client” function also need to be
refactored to consume the refactored “CallLongRunningServiceWithCallback”
and reconcile its logic with the callback function. Now we have split the logic
into two functions, main function and callback function.
Now this is where TaskCompletionSource and Task Parallel
Library TPL comes to rescue. Using TaskCompletionSource you can refactor the
above code as follows:
private Task<string> CallLongRunningServiceWithCallback()
{
var tcs = new TaskCompletionSource<string>();
var client = new ServiceReference1.Service1Client();
client.LongFunctionCallback
+= (o, args) =>
{
if
(args.Error != null)
{
tcs.TrySetException(args.Error);
return;
}
if (args.Cancelled)
{
tcs.TrySetCanceled();
return;
}
tcs.TrySetResult(args.Result);
};
client.RunLongFunctionAsync();
// Do some other work…
return tcs.Task;
}
Client application will get the task as return parameter and
will have to call task.Result to get the result from long running function. In
the above see the comment line “Do some other work…”. This is where you can do
something else while the long function is doing its job. Following is the code
you will write to consume the long running function with callback.
Task<string> t =
CallLongRunningFunctionWithCallback();
Console.WriteLine(t.Result);
Where is the blocking call here? In above lines of code when
you call t.Result it’s a blocking call on current thread. You need this anyway
because we don’t know how long the long function will take to complete. But the
best thing about this pattern that I allows us to not just wait but we can
carry other activities during the function is running. Also see TPL code is
much simpler than our earlier when callback was a separate function. TPL provides TaskCompletionSource to help us track
the result of the async task in a consistent way across the projects.
No comments:
Post a Comment