How to wait for an async method to complete inside a [LispFunction] ?

How to wait for an async method to complete inside a [LispFunction] ?

bahman.jf.68
Advocate Advocate
456 Views
7 Replies
Message 1 of 8

How to wait for an async method to complete inside a [LispFunction] ?

bahman.jf.68
Advocate
Advocate

Hi everyone,

I'm working on a .NET plugin for AutoCAD (2017+) and I'm facing an issue when calling an async method ((extractOSMImagesAsync)) from a [LispFunction].

I have a function that downloads satellite map tiles using HttpClient and displays a Windows Form circular progress bar (Modeless form) during the download. The actual download logic  is asynchronous (async Task). When I call this logic from a [LispFunction], AutoCAD continues executing the rest of the code before the download finishes and before the progress form is closed.

[LispFunction("RunOSMMaps")]
public ResultBuffer RunOSMMaps(ResultBuffer resBuf)
{
    Point3d vpll = ...; // parsed from resBuf
    Point3d vpur = ...;
    
    // Other parameters like image path, zoom, etc.

    ExtractImages runmaps = new ExtractImages();

    if (CheckNet())
    {
        // This is an async Task method
        runmaps.extractOSMImagesAsync(vpll, vpur, ...); // <-- returns immediately
    }

    // This message box is shown immediately before download completes
    Application.ShowAlertDialog(" Download completed!");

    return null;
}

 
While in this Command Method it works correct, it means "Alert dialog" shows after complete downloading process : (It should be done by LispFunction)

[CommandMethod("RunOSMMaps")]
        public async void RunOSMMapsCommand()
        {
            Point3d vpll = ...; // parsed from resBuf
            Point3d vpur = ...;

            if (CheckNet())
            {
                // This is an async Task method
                await runmaps.extractOSMImagesAsync(vpll, vpur...);
                );
            }

            Application.ShowAlertDialog(" Download completed!");
        }


Any suggestions or examples are appreciated!

Thanks in advance,

0 Likes
457 Views
7 Replies
Replies (7)
Message 2 of 8

ActivistInvestor
Mentor
Mentor

Your CommandMethod works as you expect because it is marked as async and it uses await  which is what causes the compiler to rewrite your code so that whatever follows the await-ed method call does not run until the await-ed method has returned.

 

Adding the same async and await keywords to the LispFunction should make it work like the CommandMethod::

[LispFunction("RunOSMMaps")]
public async ResultBuffer RunOSMMaps(ResultBuffer resBuf)
{
    Point3d vpll = ...; // parsed from resBuf
    Point3d vpur = ...;
    
    // Other parameters like image path, zoom, etc.

    ExtractImages runmaps = new ExtractImages();

    if (CheckNet())
    {
        // This is an async Task method
        await runmaps.extractOSMImagesAsync(vpll, vpur, ...); 
    }

    // This message box is shown immediately before download completes
    Application.ShowAlertDialog(" Download completed!");

    return null;
}

 

The only difference is that the method is marked as async and the call to extractOSMImagesAsync() is preceded by await.

 

However, if you are expecting the calling LISP code to wait until extractOSMImagesAsync() has returned, that is not going to happen. The LispFunction will return to its caller before extractOSMImagesAsync() has returned, and the only way to prevent that is by not using async/await.

0 Likes
Message 3 of 8

BlackBox_
Advisor
Advisor

What happens if you silently invoke the CommandMethod from your LispFunction? 


"How we think determines what we do, and what we do determines what we get."

Sincpac C3D ~ Autodesk Exchange Apps

0 Likes
Message 4 of 8

bahman.jf.68
Advocate
Advocate

I checkde your method and gives this error : 

Severity	Code	Description	Project	File	Line	Suppression State
Error	CS1983	The return type of an async method must be void, Task, Task<T>, a task-like type, IAsyncEnumerable<T>, or IAsyncEnumerator<T>	

 It seems [LispFunction] doesn't support "async ResultBuffer".

 

Just to clarify my previous setup:

  • When I was using the [extractOSMImages] method without async/await, and the progress form was shown as modal, everything worked fine. The entire [LispFunction] completed before returning control to the calling LISP code, and there were no issues.
  •  
  • I switched the progress form to modeless because in modal mode, the progress bar was flickering and not updating smoothly.
  •  
  • I also changed the download logic to be async so that the [HttpClient] requests could run asynchronously, which significantly improves download speed.

 

Now with both changes (modeless form and async download), the [LispFunction] returns immediately, before the download finishes or the form closes.

0 Likes
Message 5 of 8

bahman.jf.68
Advocate
Advocate

I don't think invoking a [CommandMethod] from within a [LispFunction] would solve the issue. Even if the command is launched successfully, the [LispFunction] itself will return immediately and continue executing the remaining LISP code — while the [CommandMethod] is still running asynchronously. So we end up with the same problem: the LISP side doesn’t wait for the download or async task to complete.

0 Likes
Message 6 of 8

ActivistInvestor
Mentor
Mentor

@bahman.jf.68 wrote:

I checkde your method and gives this error : 

Severity	Code	Description	Project	File	Line	Suppression State
Error	CS1983	The return type of an async method must be void, Task, Task<T>, a task-like type, IAsyncEnumerable<T>, or IAsyncEnumerator<T>	

 It seems [LispFunction] doesn't support "async ResultBuffer".

 

A LispFunction can return void:

public async void RunOSMMaps(ResultBuffer resBuf)
{
   ...
}

 

However, based on your comments, I think you're expecting the calling LISP code to wait until the download operation is completed, which it will not. Even with async/await you cannot stop a LispFunction from returning before the await'ed method call returns. await only delays execution of the code in the async method that follows await, but the method itself will still return before the await'ed method call returns.

 

If there's progress dialog involved, things can be much more complicated.

0 Likes
Message 7 of 8

bahman.jf.68
Advocate
Advocate

somewhat I knew that [LispFunction] runs as sync, actually I was looking for a way to stop running the rest of the Lisp code when calling [extractOSMImagesAsync] and before completing it by another way.
Thanks again.

0 Likes
Message 8 of 8

ActivistInvestor
Mentor
Mentor

@bahman.jf.68 wrote:

somewhat I knew that [LispFunction] runs as sync, actually I was looking for a way to stop running the rest of the Lisp code when calling [extractOSMImagesAsync] and before completing it by another way.
Thanks again.


You can prevent your LispFunction from returning until your asynchronous method call returns by just not using async/await. But in that case, you also have to deal with blocking the UI.

 

See if you can do something with this:

[LispFunction("RunOSMMaps")]
public ResultBuffer RunOSMMaps(ResultBuffer resBuf)
{
   Point3d vpll = ...; // parsed from resBuf
   Point3d vpur = ...;

   // Other parameters like image path, zoom, etc.

   ExtractImages runmaps = new ExtractImages();

   if (CheckNet())
   {
      var start = DateTime.Now;

      try   // <- Not optional - If an exception leaks from this method, it can crash AutoCAD
      {
         var task = runmaps.extractOSMImagesAsync(vpll, vpur, ...);
         
         /// Don't allow continuation to run on worker thread:
         task.ConfigureAwait(false);
         
         var elapsed = DateTime.Now - start;

         // Set a timeout that aborts the operation if
         // it takes longer than a specified duration
         // (30 seconds in this example):
         var timeout = TimeSpan.FromSeconds(30); 

         while(!task.IsCompleted)
         {
            elapsed = DateTime.Now - start;
            if(elapsed > timeout)
            {
               UpdateUI("\nError - operation timed out", 0);
               return null;
            }
            UpdateUI($"\rDownloading files {elapsed.TotalSeconds:F2}", 500);
         }

         task.GetAwaiter().GetResult();  // forces completion of the async operation
      }
      catch(System.Exception ex)
      {
         UpdateUI($"\nError: {ex.ToString()}");
         return null;
      }

      var duration = (DateTime.Now - start).Seconds;
      UpdateUI($"\nDownload Completed in {duration:F2} seconds.\n");

   }

   return null;
}

static void UpdateUI(string msg, int duration = 0)
{
   Application.DocumentManager.MdiActiveDocument?
      .Editor.WriteMessage(msg);

   if(duration > 0)
      Thread.Sleep(duration);
}

 

0 Likes