Happy Friday! This week I am excited to welcome back Andreas Brake as the guest writer for this post. Andreas was my partner in crime for our project StreamVR which won the Best Overall Project Award at the ENGworks Hackathon. He is a software engineer at Topgolf Media doing server development. In his free time, he enjoys hiking, gaming, and gardening. This week, he will discuss how to perform async operations within Revit, a feature we used when working on StreamVR.
When developing Revit plug-ins, one will eventually run into the issue where the structure of the standard "tutorial" plug-in has a specific shortcoming: this is the inability to run in parallel or in the background with Revit or other plug-ins. This blog post will be going over ways to structure your plug-in to allow for asynchronous and multi-threaded activity.
For a standard add-in with a ribbon, your application has one main class that implements "IExternalApplication" this class is registered in your ".addin" file and acts as your entry point. This class defines two methods named "OnStartup" and "OnShutdown" that are called when Revit is started and shut down. These hooks are frequently used to create ribbon buttons that trigger additional commands.
Commands are classes that implement "IExternalCommand". They implement a method called Execute which, as the name would imply, is the method that is called when your command is executed. In simpler plug-ins, you can also just have a command which is registered in your ".addin" file.
When a command is executed, it blocks other activity within Revit. This means that you cannot run other plug-ins at the same time or run asynchronous operations within the plug-in that still operate on the Revit DB.
The major change we are doing to allow for background operations is to have our main application contain a "static" reference to itself. This static reference is assigned "OnStartup" and will act as a singleton that can be used by your other commands. It will persist throughout the Revit application lifecycle. This singleton can be used to create listeners for asynchronous events, for example a form that takes user input. We ensure that events from this form trigger a class that implements "IExternalEventHandler" which will perform additional background operations that can interact with the Revit DB.
The code below shows the application code that registers itself as a singleton.
Later a command is triggered by a button on the Revit ribbon which then calls "ShowFormInBackground" on this singleton.
"ShowFormInBackground" creates instances of all "ExternalEvent"s that will be triggered by our async operation, in this case a button press in a WPF form. The method also creates and shows the form, passing through the event objects for later usage.
Because the "ExternalEvent" objects are passed to the WPF form, we can call the "Raise" method on these events when a user clicks a button in the form. When "Raise" is called on a class that implements "IExternalEventHandler", the "Execute" method is triggered with a reference to the UIApplication. This means that we can now run additional operations against the RevitAPI including updating elements all without relying on a command that blocks all other operations within Revit.
because "ExternalEvent"s are intended to be called by external sources, we can even go so far as to trigger this event within a Task which will run elsewhere within the ThreadPool!