Lieven De Foor (b-inside) asked a question.
Hi,
To instantiate a WebView2 component we need to deal with .NET asynchronous (async) methods.
In C# you can "await" these methods, which is a keyword/concept that is not available in ABL/OpenEdge.
Simply calling an async method without await causes it to return a (running) Task<ReturnType>, while calling it with await waits for the task to complete (asynchronously) and returns a ReturnType (simply put; what actually happens is explained in detail here: https://blog.stephencleary.com/2012/02/async-and-await.html)
In ABL we can only do the call without await, causing a Task<> to be returned which might not be completed yet.
I've seen code by a colleague using Task<>:GetAwaiter():GetResult(), but these should not be used according to Microsofts documentation:
"This method is intended for compiler use rather than use directly in code."
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.getawaiter
You can use Task<ReturnType>:Wait(), which waits (blocks) for the task to complete.
After that you can get the Task<ReturnType>:Result, which is of type ReturnType.
But that doesn't seem to work as expected in all cases.
Example C# code to initialize a WebView2 component:
- CoreWebView2Environment env = await CoreWebView2Environment:CreateAsync(...);
- await webView2:EnsureCoreWebView2Async(env);
The first async call can be written in ABL as follows, and seems to work:
- createEnvironmentTask = CoreWebView2Environment:CreateAsync(...).
- createEnvironmentTask:Wait().
- env = createEnvironmentTask:Result.
The EnsureCoreWebView2Async() call however can't be written in a similar way, as that seems to hang the session...
We can call it without await and subscribe to the CoreWebView2Initialization event, which seems like a workaround with some limitations...
What is the correct way to work with this kind of methods from ABL?
What is and is not supported?
There is not much info online regarding OpenEdge + .NET async calls.
http://www.fabianffrank.com/2014/02/consuming-task-based-asynchronous-methods-from-openedge-part1.html is something, but I'm not sure this is a complete equivalent to the await keyword...
Fabian Frank uses Task.ContinueWith(), but according to https://stackoverflow.com/questions/18965200/difference-between-await-and-continuewith and https://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda it's similar but not exactly the same as await...
Regards,
Lieven
The issue we're currently having is that once we enable client logging in the session (using LOG-MANAGER handle), any WebView2 component we try to show fails on initialization with a generic exception...
Once broken this can't be fixed in the sessions lifetime, even after disabling client logging again...
This only happens on VM's, on developer local devices no problem...
That sounds nasty! I would recommend posting that detail to Progress tech support.
The problem with this kind of issues is providing a reproducible without investing too much time...
There's no need to call Wait(). The doc notes that the "Result" property from the Task<TResult> class will take care of it for you.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.result?view=netframework-4.8
This can be simplified down to:
Thanks @Matt Baker (Progress Software).
> Accessing the property's get accessor blocks the calling thread until the asynchronous operation is complete; it is equivalent to calling the Wait method.
We've narrowed down the WebView2 initialization issues to the following
Scenario:
If the classic Window is opened and closed before any WebView2 is started, all WebView2 screens start without issues.
If all open WebView2 screens are closed before the the classic Window is opened and closed, then new WebView2 screens also don't have a problem.
There must be something special happening in the session when the first classic Window is shown standalone (i.e. not in a WindowContainer as that works fine) that disturbs further WebView2's (but only in the above scenario)
@Lieven De Foor (b-inside) I'd need a working (problematic) example to experiment with. EnsureCoreWebView2Async returns Task, not Task<TResult> it does not have a result property. My guess is you could spin with a repeat block containing a "PROCESS EVENTS" against Task:IsCompleted. (but only a guess). I know calling the Task:Wait() hangs the session, but I didn't dig into why. With a working example, I could maybe figure out why.
We currently do:
after which we return.
In the event handler myWebView_CoreWebView2InitializationCompleted we then continue with whatever we wanted to load in the WebView2 component.
That seems to work fine, except in the scenario described above where a classic Window messes things up...
We'll see if it's possible to create a reproducible scenario outside of our application, but as always this might either not be possible or take a huge amount of time...
I'm contemplating creating a dummy classic Window in our WebView UserControl right before initializing the WebView2 component, as that might be a quick workaround...
What is the correct approach for utilizing .NET asynchronous methods in ABL/OpenEdge, considering the absence of the "await" keyword and the limitations of using Task<>?
It depends on the scenario. In some cases, using the blocking Result property getter from Task<> is enough. It will block the UI, but that might be acceptable. In other scenarios, not fetching the return value at all might be acceptable (depends on the use case). In other scenarios, neither is acceptable.
It is this third group, I'm concerned about.
Generally, starting with trying to use Task<>:Result.
I implemented CefSharp (Chromium) and had more success outsourcing any asynch behavior to the .NET side by creating a wrapper class/DLL. The resulting class fires events that ABL can consume. The class can use all .NET functionality. I check for Control.Invoke for any event that will be consumed by ABL.