I got a question on the message board a week ago regarding calling async .NET methods using wwDotnetBridge. My immediate suspicion was that this probably wouldn't be possible since async code on .NET usually uses generics and requires special setup.
However, as it turns out, you can call async methods in .NET with wwDotnetBridge. In this post I describe how async/await methods work in .NET and how you can call them from FoxPro with wwDotnetBridge.
How async/await works in .NET
The async
and await
pattern in .NET seems like magic - you make a request to a function that processes asynchronously, which means the code runs in the background and then continue processing the code as if it was running synchronously. async
and await
code looks and feels just like synchronous code but behind the covers, the code actually runs asynchronously.
.NET does this via some magic with the compiler that effectively re-writes you linear code into a state machine. That generated code essentially creates the dreaded async pyramid of doom that nobody wants to code up, but hides it behind generated compiler code - you never look at the series of continuations.
At a lower level, .NET uses the Task
or Task<t>
class API which is like a .NET version of a promise. Task is essentially task forwarder, that calls a method asynchronously, then handles the callback and provides a Result
property that has a result value (if any). There are options to check completion status as well as methods that can wait for completion. In fact you can just choose to wait on .Result
which is a blocking getter
that won't return until the result returns.
Task
is the low level feature - async
and await
is language sugar built around the Task
object that essentially builds a state machine that waits for completion internally and includes checks and methods that can check the current state of the async request. Methods exist to wait for completion, to continue processing with the result (.ContinueWith()
which is what async
uses) as well as a .Result property that blocks until the result is available.
In essence, async
and await
chops up linear code into nested blocks code that continue in a linear fashion. For the developer the beauty of async await is that it looks and behaves mostly like linear code while running asynchronously and freeing up the calling thread.
An example of Async/Await in .NET
Let's say I want to make an HTTP call with System.Net.WebClient
which has a number of async methods.
public async Task<string> MakeHttpCall(string url)
{
var client = new WebClient();
string http = await client.DownloadStringTaskAsync(url);
return http;
}
Remember the magic here - the compiler is doing a bunch of stuff to fix up this code. Note in order for async
await
to work the method called has to be async
to start with, which means the caller has to be calling asynchronously. Async await can be a real rabbit hole as it worms its way up the stack until reach a place where an Async method can be started (usually an event or server generated action). Another way to is to Task.Run()
to kick of your own Task
to kick of an async
operation sequence.
Also note the compiler magic that makes it possible for the method to return Task<string>
, but the code actually returning a string
. Async methods automatically fix up any result type into a task so the string result becomes Task<string>
.
To be clear when we want to call an async method from FoxPro, we can't use this same approach, but there are other ways to retrieve results from Async call in our non-async capable, event less Foxpro environment.
It's also possible to call async methods without using await. As seen above, an async method is really just a method that returns a Task
. So WebClient.DownloadStringTaskAsync() - which is an async
method that normally is called with async
await
, can also be called like this:
public string MakeHttpCall(string url)
{
var client = new WebClient();
var task = client.DownloadStringTaskAsync(url); // returns immediately
// waits until Result is available
string http = task.Result;
return http;
}
Here the code is directly working with the lower level Task API and it uses the .Result
property to wait for completion. If .Result
is not ready yet, retrieving .Result
blocks and waits for completion of the async task before the value is returned.
This pretty much defeats the purpose of async since we end up waiting for the result, but keep in mind that you do have the option of running other code between the retrieval of the Task and getting the Result
property.
This code looks like something that we can call with wwDotnetBridge.
Calling an Async method with wwDotnetBridge
And as it turns out we can in fact call DownloadStringTaskAsync()
with FoxPro code just like this:
do wwDotNetBridge
LOCAL loBridge as wwDotNetBridge
loBridge = CreateObject("wwDotNetBridge","V4")
loClient = loBridge.CreateInstance("System.Net.WebClient")
*** execute and returns immediately
loTask = loBridge.InvokeMethod(loClient,"DownloadStringTaskAsync","https://west-wind.com")
? loTask && object
*** Waits for completion
lcHtml = loBridge.GetProperty(loTask,"Result")
? lcHtml
And this works just fine.
Note that you have to call the async method indirectly with InvokeMethod()
and you have to retrieve the Result
value from the Task<T>
Result
using GetProperty()
. This is required because both the method and the result property use .NET generics and those can't be called directly through COM interop and require wwDotnetBridge's indirect processing. But it works! Yipiee!
wwDotnetBridge - More that you think!
I was pretty convinced that this wasn't going to work, but in hindsight it makes perfect sense that it does. Async methods are just methods that return a Task
object, which can be accessed and manipulated like any other object in .NET and therefore with wwDotnetBridge. The main consideration for wwDotnetBridge is that Task<t>
is a generic type and requires indirect access using InvokeMethod()
to call the async method, and using GetProperty()
to retrieve the Result property.
Be careful
All that said, I'm not sure if it's a great idea to actually do this. Async methods run in the background and potentially on background threads and Microsoft strongly recommends you don't use .Result
to wait for completion. They are of the "don't call us we call you!" persuasion, by using aync
await
, or by using Task continuations (ie. .ContinueWith(result)
which is something we can't do directly with wwDotnetBridge (can't create delegates).
However, if you are running inside of FoxPro over COM (as we are with wwDotnetBridge) there's already thread marshalling happening that should prevent any thread conflicts from manifesting with async code. Running a few tests firing off 10 simultaneous requests and collecting them seems to work reliably even for long runs. Still make sure you test this out to make sure you don't run into thread lock up or corruption. Check, test and be vigilant if you go down this path.
So there you have it: Task Async methods with wwDotnetBridge are possible. More APIs to connect with for FoxPro. Rock on!
