Correct use of .NET asynchronous methods?

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:

  1. CoreWebView2Environment env = await CoreWebView2Environment:CreateAsync(...);
  2. await webView2:EnsureCoreWebView2Async(env);

The first async call can be written in ABL as follows, and seems to work:

  1. createEnvironmentTask = CoreWebView2Environment:CreateAsync(...).
  2. createEnvironmentTask:Wait().
  3. 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


  • Lieven De Foor (b-inside)

    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...

    • Mike Fechner (Consultingwerk)

      That sounds nasty! I would recommend posting that detail to Progress tech support.

      • Lieven De Foor (b-inside)

        The problem with this kind of issues is providing a reproducible without investing too much time...

  • Matt Baker (Progress Software)

    1. createEnvironmentTask = CoreWebView2Environment:CreateAsync(...).
    2. createEnvironmentTask:Wait().
    3. env = createEnvironmentTask:Result.

    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:

    1. env = CoreWebView2Environment:CreateAsync(...):Result.

     

    • Lieven De Foor (b-inside)

      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:

      • All currently open screens are .NET forms, either hosting .NET controls or a classic ABL Window in a WindowContainer.
      • Open a screen that has a WebView2 component. This starts OK. Keep this screen open.
      • Open and close a classic Window (.w file)
      • Open another screen with WebView2 component -> initialization error.

       

      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)

      • Matt Baker (Progress Software)

        @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.

  • Lieven De Foor (b-inside)

    We currently do:

    1. ...
    2. myWebView:CoreWebView2InitializationCompleted:Subscribe(myWebView_CoreWebView2InitializationCompleted).
    3. myWebView:EnsureCoreWebView2Async(Environment).

    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...

  • hashir shabbir (Customer)

    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<>?

    • Matt Baker (Progress Software)

      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.

  • Joe Q (Customer)

    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.