09.08.2015 Views

Parallel programming in an AutoCAD application - Autodesk

Parallel programming in an AutoCAD application - Autodesk

Parallel programming in an AutoCAD application - Autodesk

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Parallel</strong> <strong>programm<strong>in</strong>g</strong> <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong> <strong>application</strong>Gop<strong>in</strong>ath Taget – <strong>Autodesk</strong> Inc.CP2526In this class, we will discuss the problems with implement<strong>in</strong>g parallelism <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong> .NET <strong>an</strong>d C++<strong>application</strong>. You will learn which portions of <strong>an</strong> <strong>application</strong> c<strong>an</strong> be safely parallelized. We will also coverwhat technologies the latest .NET <strong>an</strong>d C++ libraries provide to build parallelism naturally <strong>an</strong>d easily <strong>in</strong>toyour <strong>AutoCAD</strong> <strong>application</strong>. You will learn how to synchronize between threads <strong>in</strong> your <strong>application</strong> <strong>an</strong>d<strong>AutoCAD</strong> functionality.About the Speaker:Gop<strong>in</strong>ath is a member of the <strong>Autodesk</strong> Developer Technical Services Team. He has more th<strong>an</strong> sevenyears of experience develop<strong>in</strong>g <strong>an</strong>d support<strong>in</strong>g <strong>AutoCAD</strong>® APIs, <strong>in</strong>clud<strong>in</strong>g ObjectARX®, Microsoft®.NET, VBA <strong>an</strong>d LISP. Gop<strong>in</strong>ath also has several years of experience <strong>in</strong> software development on otherCAD platforms, <strong>in</strong>clud<strong>in</strong>g MicroStation®, SolidWorks®, <strong>an</strong>d CATIA® ma<strong>in</strong>ly us<strong>in</strong>g C++ <strong>an</strong>d technologiessuch as MFC <strong>an</strong>d COM. Gop<strong>in</strong>ath was also <strong>in</strong>volved <strong>in</strong> the development of Web-based <strong>application</strong>s for<strong>Autodesk</strong>® MapGuide® <strong>an</strong>d <strong>AutoCAD</strong> Map. Gop<strong>in</strong>ath has master's degrees <strong>in</strong> Civil Eng<strong>in</strong>eer<strong>in</strong>g <strong>an</strong>dSoftware Systems.


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® Applicationpublic void Term<strong>in</strong>ate(){}void BackgroundProcess(){// this is to represent the background processSystem.Thread<strong>in</strong>g.Thread.Sleep(5000);}// now we need to marshal the call to the ma<strong>in</strong> threadif (syncCtrl.InvokeRequired) // I don't see how this could ever be false// <strong>in</strong> this context, but I check it <strong>an</strong>yway//syncCtrl.Invoke(new F<strong>in</strong>ishedProcess<strong>in</strong>gDelegate(F<strong>in</strong>ishedProcess<strong>in</strong>g));syncCtrl.Beg<strong>in</strong>Invoke(new F<strong>in</strong>ishedProcess<strong>in</strong>gDelegate(F<strong>in</strong>ishedProcess<strong>in</strong>g));elseF<strong>in</strong>ishedProcess<strong>in</strong>g();delegate void F<strong>in</strong>ishedProcess<strong>in</strong>gDelegate();void F<strong>in</strong>ishedProcess<strong>in</strong>g(){// if we w<strong>an</strong>t to modify the database, then we need to lock the document// s<strong>in</strong>ce we are <strong>in</strong> session/<strong>application</strong> contextDocument doc = acApp.DocumentM<strong>an</strong>ager.MdiActiveDocument;us<strong>in</strong>g (doc.LockDocument()){us<strong>in</strong>g (Tr<strong>an</strong>saction tr = doc.Database.Tr<strong>an</strong>sactionM<strong>an</strong>ager.StartTr<strong>an</strong>saction()){BlockTable bt = (BlockTable)tr.GetObject(doc.Database.BlockTableId,OpenMode.ForRead);BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace],OpenMode.ForWrite);L<strong>in</strong>e l<strong>in</strong>e = new L<strong>in</strong>e(new Po<strong>in</strong>t3d(0, 0, 0), new Po<strong>in</strong>t3d(10, 10, 0));ms.AppendEntity(l<strong>in</strong>e);tr.AddNewlyCreatedDBObject(l<strong>in</strong>e, true);}}tr.Commit();}// also write some message to the comm<strong>an</strong>d l<strong>in</strong>e// Note: us<strong>in</strong>g <strong>AutoCAD</strong> notification bubbles would be a nicer solution :)// TrayItem/TrayItemBubbleW<strong>in</strong>dowEditor ed = acApp.DocumentM<strong>an</strong>ager.MdiActiveDocument.Editor;ed.WriteMessage("F<strong>in</strong>ished the background process!\n");[Comm<strong>an</strong>dMethod("ProcessInBackground")]public void ProcessBackground(){// let's say we got some data from the draw<strong>in</strong>g <strong>an</strong>d// now we w<strong>an</strong>t to process it <strong>in</strong> a background thread5


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® ApplicationSystem.Thread<strong>in</strong>g.Thread thread = new System.Thread<strong>in</strong>g.Thread(new System.Thread<strong>in</strong>g.ThreadStart(BackgroundProcess));thread.Start();}Editor ed = acApp.DocumentM<strong>an</strong>ager.MdiActiveDocument.Editor;ed.WriteMessage("Started background process<strong>in</strong>g. You c<strong>an</strong> keep work<strong>in</strong>g as usual.\n");}Another way to communicate between UI thread <strong>an</strong>d worker thread is to use the .NETSystem.ComponentModel.BackgroundWorker type. This type was specifically designed to makecommunication between UI threads <strong>an</strong>d background threads simple. Here is a sample of howthis type could be used with <strong>AutoCAD</strong> .NET:public class BkWorkerClass{// BackgroundWorker is a class provided by .NET 2.0 that makes// it easy to perform asynchronous operations <strong>in</strong> a background threadBackgroundWorker _bw;[Comm<strong>an</strong>dMethod("bkworkerlaunch")]public void BkWorkerLaunch(){// We are go<strong>in</strong>g to use asynchronous method to do work.// For this we use built <strong>in</strong> .NET class called// BackgroundWorker. This class makes it easy to launch a thread,// do our stuff <strong>an</strong>d synchronize back with// the ma<strong>in</strong> UI thread, <strong>in</strong> this case the modeless custom Palette.if (null == _bw){// Inst<strong>an</strong>tiate the backgroundworker class_bw = new BackgroundWorker{WorkerReportsProgress = true,WorkerSupportsC<strong>an</strong>cellation = true};// This is the event h<strong>an</strong>dler that actually does the work_bw.DoWork += new DoWorkEventH<strong>an</strong>dler(_bw_DoWork);// This event h<strong>an</strong>dler gets called when there is a ch<strong>an</strong>ge <strong>in</strong> state_bw.ProgressCh<strong>an</strong>ged += new ProgressCh<strong>an</strong>gedEventH<strong>an</strong>dler(_bw_ProgressCh<strong>an</strong>ged);// This event h<strong>an</strong>dler gets called when the BackgroundWorker is done._bw.RunWorkerCompleted += newRunWorkerCompletedEventH<strong>an</strong>dler(_bw_RunWorkerCompleted);}_bw.RunWorkerAsync(); // Start the background worker}Editor ed = acApp.DocumentM<strong>an</strong>ager.MdiActiveDocument.Editor;ed.WriteMessage("Started background process<strong>in</strong>g.You c<strong>an</strong> keep work<strong>in</strong>g as usual.\n");6


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® Application#region "The BackgroundWorker DoWork event h<strong>an</strong>dler implementation"// This event h<strong>an</strong>dler is where the action is. We call the the Service Method herevoid _bw_DoWork(object sender, DoWorkEventArgs e){// this is to represent the background processSystem.Thread<strong>in</strong>g.Thread.Sleep(5000);}#endregion#region "The BackgroundWorker ProgressCh<strong>an</strong>ged event h<strong>an</strong>dler implementation"// This event h<strong>an</strong>dler gets called when the BackgroundWorker.ReportProgress method// gets called <strong>in</strong> the DoWork event. This event is special because it is run <strong>in</strong> the// Ma<strong>in</strong> thread. That is the magic of the BackgroundWorker classvoid _bw_ProgressCh<strong>an</strong>ged(object sender, ProgressCh<strong>an</strong>gedEventArgs e){}#endregion#region "The BackgroundWorker RunWorkerCompleted event h<strong>an</strong>dler implementation"// This event h<strong>an</strong>dler gets called when the BackgroundWorker is done// with what it is do<strong>in</strong>g.// In our implementation we don't do much but just access the exception// if <strong>an</strong>y when the background worker was runn<strong>in</strong>g.void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){System.Exception exp = e.Error; // Get the error if <strong>an</strong>y// if we w<strong>an</strong>t to modify the database, then we need to lock the document// s<strong>in</strong>ce we are <strong>in</strong> session/<strong>application</strong> contextDocument doc = acApp.DocumentM<strong>an</strong>ager.MdiActiveDocument;us<strong>in</strong>g (doc.LockDocument()){us<strong>in</strong>g (Tr<strong>an</strong>saction tr = doc.Database.Tr<strong>an</strong>sactionM<strong>an</strong>ager.StartTr<strong>an</strong>saction()){BlockTable bt = (BlockTable)tr.GetObject(doc.Database.BlockTableId, OpenMode.ForRead);BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);L<strong>in</strong>e l<strong>in</strong>e = new L<strong>in</strong>e(new Po<strong>in</strong>t3d(0, 0, 0), new Po<strong>in</strong>t3d(10, 10, 0));ms.AppendEntity(l<strong>in</strong>e);tr.AddNewlyCreatedDBObject(l<strong>in</strong>e, true);}}tr.Commit();}// also write some message to the comm<strong>an</strong>d l<strong>in</strong>e// Note: us<strong>in</strong>g <strong>AutoCAD</strong> notification bubbles would be a nicer solution :)// TrayItem/TrayItemBubbleW<strong>in</strong>dowEditor ed = acApp.DocumentM<strong>an</strong>ager.MdiActiveDocument.Editor;ed.WriteMessage("F<strong>in</strong>ished the background process!\n");7


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® Application#endregion}Both the techniques above are legitimate techniques for communicat<strong>in</strong>g between UI thread <strong>an</strong>dworker thread. They both depend on one fundamental premise -that there is a delegate or eventmethod <strong>in</strong> the Ma<strong>in</strong> UI thread that c<strong>an</strong> be called synchronously or asynchronously from a workerthread to do its bidd<strong>in</strong>g. And it works. Most UI based <strong>application</strong>s both unm<strong>an</strong>aged <strong>an</strong>dm<strong>an</strong>aged use this technique to update UI controls from worker threads. It even works with<strong>AutoCAD</strong> <strong>in</strong> some situations. However, there is a problem. Consider the follow<strong>in</strong>g workflow:1) Build a .NET <strong>application</strong> with the code above (the BackgroundWorker technique)2) Set a breakpo<strong>in</strong>t <strong>in</strong> the _bw_RunWorkerCompleted method3) Load the <strong>application</strong> <strong>in</strong> <strong>AutoCAD</strong> 2012 <strong>an</strong>d run the comm<strong>an</strong>d “bkworkerlaunch”4) Immediately launch LINE comm<strong>an</strong>d. Do not perform <strong>an</strong>y action here.You will observe that the breakpo<strong>in</strong>t <strong>in</strong> _bw_RunWorkerCompleted is hit even though the LINEcomm<strong>an</strong>d is active. And this is the fatal flaw. None of the thread communication orsynchronization techniques are aware of <strong>AutoCAD</strong> Quiescent state. i.e., they are not aware if<strong>an</strong>other operation is be<strong>in</strong>g performed <strong>in</strong> <strong>AutoCAD</strong>. The delegate or the event h<strong>an</strong>dler <strong>in</strong>voked bythe worker thread is executed irrespective of <strong>an</strong>y other active comm<strong>an</strong>d or event h<strong>an</strong>dler <strong>in</strong> the<strong>AutoCAD</strong> ma<strong>in</strong> UI. This is the surest way of corrupt<strong>in</strong>g a draw<strong>in</strong>g <strong>an</strong>d completely unacceptable.So is there a way out? The short <strong>an</strong>swer is “yes”. The solution lies <strong>in</strong> the way the <strong>AutoCAD</strong> UIthread behaves. S<strong>in</strong>ce there is only one UI thread <strong>in</strong> <strong>AutoCAD</strong>, there is only one w<strong>in</strong>dowsmessage h<strong>an</strong>dler <strong>an</strong>d w<strong>in</strong>dows messages are h<strong>an</strong>dled one after <strong>an</strong>other <strong>in</strong> a queue. i.e., onlyone w<strong>in</strong>dows message gets h<strong>an</strong>dled at <strong>an</strong>y time. So if a worker thread could post a w<strong>in</strong>dowsmessage, the <strong>AutoCAD</strong> ma<strong>in</strong> thread could h<strong>an</strong>dle the message <strong>an</strong>d perform <strong>an</strong> action on behalfof the ma<strong>in</strong> thread.The first step though is to make sure when a w<strong>in</strong>dows message from the worker thread is be<strong>in</strong>gh<strong>an</strong>dled, no other operation is <strong>in</strong> progress. This c<strong>an</strong> be accomplished by a series of checks(C++ based ARX code below):bool isQuiescent(AcApDocument* pDoc = curDoc()){return ( (pDoc != NULL)&& pDoc->isQuiescent()&& ((pDoc->lockMode() == AcAp::kNotLocked) ||(pDoc->lockMode() == AcAp::kNone))&& (acedComm<strong>an</strong>dActive() == 0) // no script or lisp is active&& (acDocM<strong>an</strong>ager != NULL)&& (acDocM<strong>an</strong>ager-><strong>in</strong>putPend<strong>in</strong>g(pDoc) == 0)&& (::GetInputState() == 0));}8


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® ApplicationIf the above function returns true, you c<strong>an</strong> be sure that no other operation is be<strong>in</strong>g performed by<strong>AutoCAD</strong>.The next step is to wire <strong>in</strong> the code necessary to register a w<strong>in</strong>dows message associated withthe worker thread. For this we c<strong>an</strong> use the W<strong>in</strong>32/MFC function RegisterW<strong>in</strong>dowMessage:UINT AU2011_Adsk_ThreadDrawMessage = 0;AU2011_Adsk_ThreadDrawMessage =::RegisterW<strong>in</strong>dowMessage(L"WM_AU2011<strong>AutoCAD</strong>Thread<strong>in</strong>gDraw");The RegisterW<strong>in</strong>dowMessage W<strong>in</strong>32 function allows you to register a custom w<strong>in</strong>dowsmessage with the W<strong>in</strong>32 messag<strong>in</strong>g system given a unique str<strong>in</strong>g <strong>an</strong>d returns a uniquemessage ID. If a message already exists for the str<strong>in</strong>g, the message ID of the exist<strong>in</strong>g messageis returned.The f<strong>in</strong>al step after this is to implement a w<strong>in</strong>dows message h<strong>an</strong>dler that will h<strong>an</strong>dle the custommessage <strong>an</strong>d register<strong>in</strong>g it with <strong>AutoCAD</strong>:acedRegisterWatchW<strong>in</strong>Msg(MessageProc);Register<strong>in</strong>g the w<strong>in</strong>dows message <strong>an</strong>d register<strong>in</strong>g the h<strong>an</strong>dler c<strong>an</strong> be conveniently performeddur<strong>in</strong>g the load event of <strong>an</strong> ARX <strong>application</strong>. Here is the implementation of the message h<strong>an</strong>dleritself along with the helper functions it uses:void MessageProc(const MSG *pMsg){// WM_AutoloaderOnProfileSwitchMsgif (pMsg->message == AU2011_Adsk_ThreadDrawMessage) {// make sure <strong>AutoCAD</strong> is quiescent, otherwise wait <strong>an</strong>d try// aga<strong>in</strong> laterif (!isQuiescent()) {static MSG msg;msg.hwnd = pMsg->hwnd;msg.message = pMsg->message;msg.wParam = pMsg->wParam;msg.lParam = pMsg->lParam;DWORD threadId;HANDLE hThread = ::CreateThread(NULL, 0, Sleeper,(LPVOID)&msg, 0, &threadId);return;}//bool bFoundApps =gAppUtil.checkAppFoldersForNewApps(AcAppUtil::kModeProfileSwitch);AddCircle();acedPostComm<strong>an</strong>dPrompt();return;}9


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® Application}//else if (pMsg->message == gnAppNotificationMessageId) {//AppNotificationM<strong>an</strong>ager::GetInst<strong>an</strong>ce()->pumpQueue();//}DWORD WINAPI Sleeper(LPVOID pParam){if (!pParam)return 0;MSG* pMsg = (MSG*)pParam;HWND hWnd = pMsg->hwnd;UINT uMsg = pMsg->message;WPARAM wParam = pMsg->wParam;WPARAM lParam = pMsg->lParam;Sleep(5000);}// post to the ma<strong>in</strong> thread we need a review of the loaded apps::PostMessage(hWnd, uMsg, wParam, lParam);return 0;static void AddCircle(){static AcGePo<strong>in</strong>t3d ctrPt=AcGePo<strong>in</strong>t3d(0,0,0);acDocM<strong>an</strong>ager->lockDocument(curDoc(), AcAp::kWrite);AcDbDatabase *pDb = curDoc()->database();AcDbObjectPo<strong>in</strong>ter pCir;{AcDbBlockTableRecordPo<strong>in</strong>terpMdlSpc(ACDB_MODEL_SPACE,pDb,AcDb::kForWrite);if(Acad::eOk==pMdlSpc.openStatus()){pCir.create();pCir->setRadius(10);pCir->setCenter(ctrPt);AcDbObjectId objId;pMdlSpc->appendAcDbEntity(objId,pCir);}}acDocM<strong>an</strong>ager->unlockDocument(curDoc());}ctrPt.setToSum(ctrPt,AcGeVector3d(15,15,0));In the code above, the message h<strong>an</strong>dler checks if <strong>AutoCAD</strong> is Quiescent <strong>an</strong>d if it is not, itcreates <strong>an</strong>other thread that sleeps for 5 seconds before resend<strong>in</strong>g the orig<strong>in</strong>al w<strong>in</strong>dows10


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® Applicationmessage aga<strong>in</strong>. Once <strong>AutoCAD</strong> is ready, a circle is drawn at specific locations with<strong>in</strong> the modelspace.This way, the mech<strong>an</strong>ism we built ma<strong>in</strong>ta<strong>in</strong>s the <strong>in</strong>tegrity of the <strong>AutoCAD</strong> UI thread while alsoexecut<strong>in</strong>g the <strong>in</strong>structions of the worker thread. Here is a simple .NET piece of code that usesthe above registered w<strong>in</strong>dows message to send messages to <strong>AutoCAD</strong> from worker threads:[Comm<strong>an</strong>dMethod("TrdsInAcad")]public void TrdsInAcad(){}if (0 == AU2011_Adsk_ThreadDrawMessage){AU2011_Adsk_ThreadDrawMessage = RegisterW<strong>in</strong>dowMessage(msgStr<strong>in</strong>g);}for (<strong>in</strong>t threadCount = 0; threadCount < 5; threadCount++){Thread t = new Thread(PostMessageToMa<strong>in</strong>Thread);t.Start();}static void PostMessageToMa<strong>in</strong>Thread(){Thread.Sleep(5000);PostMessage(Application.Ma<strong>in</strong>W<strong>in</strong>dow.H<strong>an</strong>dle, AU2011_Adsk_ThreadDrawMessage,IntPtr.Zero, IntPtr.Zero);}As you c<strong>an</strong> see, once we have a robust message h<strong>an</strong>dl<strong>in</strong>g <strong>in</strong>frastructure <strong>in</strong> place, we c<strong>an</strong> builda highly perform<strong>an</strong>t <strong>an</strong>d responsive <strong>AutoCAD</strong> <strong>application</strong> that uses a multi-core or multi-CPUarchitecture efficiently.ConclusionThough hav<strong>in</strong>g a multi-threaded <strong>application</strong> has m<strong>an</strong>y benefits, creat<strong>in</strong>g a robust multi-threaded<strong>application</strong> that actually realizes perform<strong>an</strong>ce <strong>an</strong>d responsiveness goals is hard. It is muchharder with <strong>AutoCAD</strong> <strong>application</strong>s which additionally have to deal with restrictions on the APIthat need to be used only <strong>in</strong> the ma<strong>in</strong> UI thread <strong>an</strong>d also make sure that <strong>AutoCAD</strong> works onworker thread <strong>in</strong>structions only when it is quiescent. However, once we have a robustmech<strong>an</strong>ism <strong>in</strong> place to deal with these problems, creat<strong>in</strong>g multi-threaded <strong>AutoCAD</strong> <strong>application</strong>sc<strong>an</strong> be very reward<strong>in</strong>g.In the AU class, I will talk about <strong>an</strong>d demonstrate all these issues as well as some coolthread<strong>in</strong>g models that could make design<strong>in</strong>g parallel <strong>application</strong>s a fun th<strong>in</strong>g to do.11


<strong>Parallel</strong> Programm<strong>in</strong>g <strong>in</strong> <strong>an</strong> <strong>AutoCAD</strong>® ApplicationBibliography1. Ghuloum, Anwar. What Makes <strong>Parallel</strong> Programm<strong>in</strong>g Hard? [Onl<strong>in</strong>e]http://blogs.<strong>in</strong>tel.com/research/2007/08/what_makes_parallel_programm<strong>in</strong>.php.2. posterous, meercat's. Concurrent <strong>programm<strong>in</strong>g</strong> is hard (so don't do it yourself).schmerg.com. [Onl<strong>in</strong>e] http://schmerg.com/concurrent-<strong>programm<strong>in</strong>g</strong>-is-hard-so-dont-do-it.3. Blog, Clementson's. Concurrent/<strong>Parallel</strong> Programm<strong>in</strong>g - The Next Generation. [Onl<strong>in</strong>e]http://bc.tech.coop/blog/060105.html.4. Ansari, Aria. Threads with MFC. The Code Project. [Onl<strong>in</strong>e]http://www.codeproject.com/KB/threads/threads_<strong>an</strong>d_mfc.aspx.5. Newcomer, Joseph M. Us<strong>in</strong>g User-Interface Threads. The Code Project. [Onl<strong>in</strong>e]http://www.codeproject.com/KB/threads/us<strong>in</strong>guithreads.aspx.6. Rogers, Just<strong>in</strong>. W<strong>in</strong>Forms UI Thread Invokes: An In-Depth Review ofInvoke/Beg<strong>in</strong>Invoke/InvokeRequred. Games, .NET, Perform<strong>an</strong>ce, <strong>an</strong>d More! [Onl<strong>in</strong>e]http://weblogs.asp.net/just<strong>in</strong>_rogers/pages/126345.aspx.7. Work<strong>in</strong>g With The WPF Dispatcher. Switch On The Code. [Onl<strong>in</strong>e]http://www.switchonthecode.com/tutorials/work<strong>in</strong>g-with-the-wpf-dispatcher.12

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!