Tuesday, October 10, 2017, 11:12:25 PM
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
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.
await code looks and feels just like synchronous code but behind the covers, the code actually runs asynchronously.
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.
The following retrieves an HTTP response as a string:
public async Task<string> MakeHttpCall(string url)
var client = new WebClient();
string http = await client.DownloadStringTaskAsync(url);
The C#/.NET (Roslyn) compiler is doing a bunch of stuff to fix up this code. Note in order for
await to work the method called has to be
async to start with, which means the caller has to be calling it asynchronously. Async await can be a real rabbit hole as it worms its way up the stack until it reaches a place where an
async method can be started - usually by an event or a server generated action. Another way to start an
async sequence is to use
Task.Run() to kick off your own
Task 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>. Again - compiler magic.
Clearly this is code we can't directly simulate in FoxPro, but - as it turns out we don't have to.
Under the Covers: Task based APIs
.NET does this via some magic with the compiler that effectively re-writes your linear
await code into a state machine. That generated code essentially creates the dreaded async pyramid of doom that nobody wants to code up by hand, but here the compiler hides it behind generated code so you get the benefit of it, without having to look at the pyramid code.
At a lower level, .NET uses the
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 and there are delegate methods like
ContinueWith() which is what await uses for the
await generated code.
await chops up linear code into nested blocks of 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.
You also just choose to wait on
.Result which is a blocking property getter, that won't return the result until the method completes its async task.
Task is the low level feature -
await is language sugar built around the
Task object that builds a state machine that waits for completion internally and includes checks and methods that can check the current state of the async request.
FoxPro and Task
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
await - can also simply return a
Task object and thus can also be called with this code:
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;
await code shown earlier this code blocks on the
task.Result access, so while the actual method returns immediately accessing
task.Result waits until the call finishes.
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 have the option of running other code between the retrieval of the Task and getting the
Result property. In that way you can gain some performance benefits even when using
The other reason
.Result is interesting is that this is code we can write from FoxPro with wwdotnetbridge. The method call returns a
Task and we can access the
.Result property from FoxPro.
Calling an Async method with wwDotnetBridge
So, we can in fact call
DownloadStringTaskAsync() with FoxPro code just like this:
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")
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
GetProperty(). This is required because both the method and the result property use .NET generics and those can't be accessed directly through COM interop (because it's generated code that's not in the type library) and requires wwDotnetBridge's indirect processing.
But it works and you can retrieve the HTTP result in the method above! Yipiee!
wwDotnetBridge - More that you think!
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.
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
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.
Calling Task based awaitable methods in .NET shouldn't be done to provide async functionality - it should be done merely to call methods that you can't call any other way. Otherwise I'd recommend to not use Task based APIs from FoxPro - stick with synchronous code and if necessary use
InvokeMethodAsync() to make code truly async from FoxPro.
Async with wwDotnetBridge: Use InvokeMethodAsync()
Also, keep in mind that you can call any method in .NET asynchronously with wwDotnetBridge using InvokeAsync(). Unlike calling a Task based method as described above using this approach actually provides you with a callback mechanism when the async operation completes. If you're looking for code to execute out of band,
InvokeAsync() is the way to go.
Done and Done
So there you have it: You can call Task Async methods with wwDotnetBridge - it works, but it's not necessarily the best idea. You can use it if you have to, but if possible stick with synchronous APIs or if you truly need out of band async with proper callback notifications use
Have at it!