I have written a plugin for Civil 3D that runs on the Design Automation API. The plugin works in most situations without any problems.
However, one task the plugin does is extract the solids from a Civil 3D corridor.
The call to corridor.ExportSolids
on larger corridors can take over a minute to complete, so im hitting problems with the heartbeat/timeout
[11/13/2024 21:08:53] 21:08:53.62 - Starting extraction for C_M_NLP_001 Corridor
[11/13/2024 21:08:53] Exporting solids from C_M_NLP_001|BL_M_NLP_001|RG01
[11/13/2024 21:09:54] Error: AutoCAD Core Console is shut down due to timeout.
I generally write to the AutoCAD editor quite a lot through my script to avoid this timeout, but because this is a single long running and synchronous method, there is nothing I can do while it is running.
The below question was really useful, but seems to suggest that there is no way in AutoCAD to send a message to the AutoCAD editor from anything other than the main thread, which is tied up doing the solids export.
Forge Work Item Timeouts for Design Automation API
I did try using the SynchronizationContext option to see if it would work, but all that happened is that all the messages my "Heartbeat" class generated just got output at the end of my script (when I test it on my local machine where the 1 min timeout is not an issue) so it didn't help.
public class HeartBeat : IDisposable
{
private Thread t;
private long ticks;
private Editor _editor;
private SynchronizationContext _syncContext;
private bool _running;
public HeartBeat(Editor editor, int intervalMillisec = 50000)
{
_syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
_editor = editor;
ticks = DateTime.Now.Ticks;
_running = true;
t = new Thread(() =>
{
WriteMessage($"HeartBeating every {intervalMillisec}ms.");
while (_running)
{
Thread.Sleep(intervalMillisec);
WriteMessage($"HeartBeat {(long)new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds}.");
}
});
t.Start();
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && t != null)
{
_running = false;
WriteMessage("Ending HeartBeat");
t.Join(); // Wait for the thread to finish
t = null;
}
}
private void WriteMessage(string message)
{
_syncContext.Post(_ => _editor.WriteMessage($"{message}\r\n"), null);
}
}
and i called it like this
using(new Heartbeat())
{
var corridorSolids = corridor.ExportSolids(exportParams, solidsDb);
}
Does anyone know if there is another way around this problem for AutoCAD?
Thanks Albert, that code gave me the framework for how to implement a "heartbeat" within my own tool. I did it like this:
public class HeartBeat: IDisposable
{
[DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?sendBeat@AcApHeart@@YAXXZ")]
extern static void AcApHeartSendBeat();
private CancellationTokenSource _cancellationTokenSource;
private Task _task;
public HeartBeat()
{
_cancellationTokenSource = new CancellationTokenSource();
_task = Task.Run(() =>
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
System.Threading.Thread.Sleep(1000);
AcApHeartSendBeat();
}
}, _cancellationTokenSource.Token);
}
public void Stop(CancellationToken cancellationToken)
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_task = Task.Run(() =>
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
System.Threading.Thread.Sleep(1000);
AcApHeartSendBeat();
}
}, _cancellationTokenSource.Token);
}
public void Dispose()
{
if (_cancellationTokenSource != null)
{
_cancellationTokenSource.Cancel();
_task?.Wait();
_cancellationTokenSource.Dispose();
}
}
}
So i could then put a heartbeat around any long running function like this:
using (new HeartBeat())
{
regionShapeCodeSolids = region.ExportSolids(exportParams, solidsDb);
}