Creating AutoCAD objects in an Async process extremely slow

Creating AutoCAD objects in an Async process extremely slow

Anton_Huizinga
Advocate Advocate
1,948 Views
6 Replies
Message 1 of 7

Creating AutoCAD objects in an Async process extremely slow

Anton_Huizinga
Advocate
Advocate

Probably due to a lack of fully understanding an Async process, and normally I can figure out myself by reading and testing. But now I I'm stuck in something I can't find a solution for.

 

Short story: I have an WPF application with a wizard and that generates geometry inside AutoCAD. If I use a non-async method, the speed is impressive. 10,000 circles in 0,3 seconds.

 

Anton_Huizinga_0-1709801824543.png

 

But such process blocks the current threat, thus the UI and there is no breakout option.

So, using an async method, the UI is not blocked, the progress bar is filling nicely and the user can press escape to stop processing. But drawing 10,000 circles suddenly takes much longer, almost 11 seconds, more than 30 times longer.

 

Anton_Huizinga_1-1709801838737.png

The difference in calling the code is this:

 

 

private async Task<bool> NextStepAsync()
{
    bool returnValue = true;

    try
    {
        await Task.Run(() => returnValue = DrawGeometry());
    }
    catch (System.Exception)
    {
        returnValue = false;
    }

    return returnValue;
}


private bool NextStep()
{
    bool returnValue = true;

    try
    {
        returnValue = DrawGeometry();
    }
    catch (System.Exception)
    {
        returnValue = false;
    }

    return returnValue;
}

 

And this is the function to create geometry:

 

   private bool DrawGeometry()
   {
       bool returnValue = true;

       ProgressPercent = 0;
       ElapsedTime = string.Empty;

       DateTime startTime = DateTime.Now;

       int numberOfGeometry = 10000;

       try
       {
           for (int i = 0; i < numberOfGeometry; i++)
           {
               ProgressPercent = (100.0 / (double)numberOfGeometry) * i;

               acDbServ.Database db = acAppServ.Application.DocumentManager.MdiActiveDocument.Database;
               using (acDbServ.Transaction tr = db.TransactionManager.StartTransaction())
               {
                   using (acDbServ.BlockTableRecord space = (acDbServ.BlockTableRecord)tr.GetObject(db.CurrentSpaceId, acDbServ.OpenMode.ForWrite))
                   {
                       using (acDbServ.Circle circle = new acDbServ.Circle())
                       {
                           circle.SetDatabaseDefaults();
                           circle.Center = new acGeom.Point3d(0.0, 0.0, 0.0);
                           circle.Radius = i + 1;
                           circle.Layer = "0";

                           space.AppendEntity(circle);
                           tr.AddNewlyCreatedDBObject(circle, true);
                       }
                   }
                   tr.Commit();
               }
           }
       }
       catch (System.Exception)
       {
           returnValue = false;
       }

       ElapsedTime = $"{numberOfGeometry} Circles in {(DateTime.Now - startTime).TotalMilliseconds:0} ms";

       return returnValue;
   }

 

The buttons code:

 

        private void BtnNoAsync_Click(object sender, RoutedEventArgs e)
        {
            NextStep();
        }


        private async void BtnAsync_Click(object sender, RoutedEventArgs e)
        {
            await NextStepAsync();
        }

 

 

I have the feeling it is something in the Task.Run method but I can't find anything about such an issue. Maybe it is a wrong use of a Task or Async, but I have the feeling it does not have to be such a big difference in time processing.

 

Included is a test application for who wants to try things out, or maybe someone can point me in the right direction where I can find a solution?

0 Likes
1,949 Views
6 Replies
Replies (6)
Message 2 of 7

ActivistInvestor
Mentor
Mentor

Have a look at this topic. It explains some of the differences between using/not using async/await. Also have you tried timing the execution of the code without the progress UI?

0 Likes
Message 3 of 7

Anton_Huizinga
Advocate
Advocate

In my actual application I've tested to leave the progress or just update on a modulus of 5 and it gave not a better result. So actually I did not test it in this test application. Now I did and it reduces significantly, but still it is at least 5 times slower than without the async method (but better than 30 times slower). In my actual program I did not notice any improvements on leaving out the UI update.

 

I'll have to investigate further why there is a difference in the test application and the actual application. Your link is certainly helpful though I have read thousands of such pages in the meantime to try to understand the async method. Thanks 🙂

0 Likes
Message 4 of 7

Anton_Huizinga
Advocate
Advocate

I was fooled at first by the Progress. I thought, there you go, the slowness matched my findings. But removing the Progress Property still showed a few times longer process if it was performed in a Task.

 

I changed the code, now with adding symbols instead of circles (in my application I found the delay while adding symbols, but for simplicity I used circles in my example code here, which was not a wise decision). I timed every operation in the process, which led to a log.

 

This is the log of adding one symbol to the drawing in a non-Async process:

 

 

[03/12/2024 10:03:43]  Get or create layer (elapsed time: 0ms)
[03/12/2024 10:03:43]  Get or create symbol (elapsed time: 0ms)
[03/12/2024 10:03:43]  Set new block reference properties (elapsed time: 0ms)
[03/12/2024 10:03:43]  Set block reference annotativity (elapsed time: 0ms)
[03/12/2024 10:03:43]  Set block reference scale (elapsed time: 0ms)
[03/12/2024 10:03:43]  Fill attributes (elapsed time: 0ms)
[03/12/2024 10:03:43]  Append to block attribute collection (elapsed time: 0ms)
[03/12/2024 10:03:43]  Fill attributes (elapsed time: 0ms)
[03/12/2024 10:03:43]  Append to block attribute collection (elapsed time: 0ms)
[03/12/2024 10:03:43]  Fill attributes (elapsed time: 0ms)
[03/12/2024 10:03:43]  Append to block attribute collection (elapsed time: 0ms)

 

 

And this is while in an Async process:

 

 

[03/12/2024 10:01:20]  Get or create layer (elapsed time: 0ms)
[03/12/2024 10:01:20]  Get or create symbol (elapsed time: 0ms)
[03/12/2024 10:01:20]  Set new block reference properties (elapsed time: 0ms)
[03/12/2024 10:01:20]  Set block reference annotativity (elapsed time: 0ms)
[03/12/2024 10:01:20]  Set block reference scale (elapsed time: 0ms)
[03/12/2024 10:01:20]  Fill attributes (elapsed time: 0ms)
[03/12/2024 10:01:20]  Append to block attribute collection (elapsed time: 9ms)
[03/12/2024 10:01:20]  Fill attributes (elapsed time: 0ms)
[03/12/2024 10:01:20]  Append to block attribute collection (elapsed time: 5ms)
[03/12/2024 10:01:20]  Fill attributes (elapsed time: 0ms)
[03/12/2024 10:01:20]  Append to block attribute collection (elapsed time: 7ms)

 

 

As you can see, the action to add the attribute to the block attribute collection takes 5 to 9 ms each, compared to the non-Async action which is close to 0 ms. So with 3 attributes, adding a symbol takes up to 20 ms more, which is obviously noticeable when adding thousands of symbols at once.

 

But this happens when I insert an Annotative block. If I insert a non-Annotative block, the elapsed time of appending the attribute to the collection is reduced to 1 to 3 ms in an Async process. But weird enough, in a non-Async process it is very fast, also Annotative blocks.

 

So, non-Async is fast, but user has no control of the dialog. Async is noticeable slower but user has control of the dialog, and Annotative blocks in an Async process is extremely slow...

 

0 Likes
Message 5 of 7

ActivistInvestor
Mentor
Mentor

Those kind of results are roughly what I would expect for an asynchronous operation versus a synchronous operation. Async has some overhead associated with managing the asynchronous workflow, such as context switching and task scheduling.

 

With an asynchronous operation you could probably get much slower results by just moving the mouse around while the operation is underway.

0 Likes
Message 6 of 7

527774340
Observer
Observer

First, database operations in CAD are performed on the main thread. Using Task.Run can lead to frequent thread switching, so you should use Dispatcher.InvokeAsync instead. Additionally, assigning a value to ProgressPercent triggers OnPropertyChanged on every update, which causes significant overhead due to UI re-rendering. To address this, you can use a Timer for periodic rendering or reduce the frequency of rendering. Here is the updated C# file with improvements:

0 Likes
Message 7 of 7

Anton_Huizinga
Advocate
Advocate

Thank you! I'll test your example code to see if it improves the speed.

0 Likes