Rhino Inside Revit limitation: IExternalEventHandler deadlock in GH

We’re using an async wrapper around an implementation of the IExternalEventHandler and have found that it runs into a deadlock when executing through a GH C# node. This is problematic since this procedure forms part of our strategy for integration testing. Essentially, the external event in Revit is waiting for GH to complete what its doing, but GH is waiting for Revit to finish completing its external event and their in lies the problem; a deadlock arises. Is there any workaround?

Hi @thomas7,

Grasshopper itself is running inside an IExternalEventHandler and Revit does not allow two Events run at the same time, it is not multithread.

You would experience same deadlock if you have two EventHandlers in your code one waiting the other.

Why do you need to fire an ExternalEvent inside a Grasshopper component?

I mean while in a Grasshopper component SolveInstance you are already in Revit API context.

Hi @kike we’re testing small parts of a Revit application in GH as part of our integration testing. Since the application is asynchronous and has a services startup pattern which requires an IExternalEventHandler we cant simply remove it for GH purposes - all of these services have to startup before we can run the units of code. So to clarify, its a WPF app that runs in Revit, not GH, but we need to use GH for debugging (RiR is used exclusively in the backend) - all the geometry processing is done via RhinoCommon.

Is there anyway to force control temporarily back to Revit so the external event can fire? I looked at yielding but this failed to work.

It seems like the only option is to run the barebones services via a temporary ‘debug’ Revit app and use a singleton which GH can access as a backdoor. Pretty frustrating for anyone developing async apps who have to test in Rhino.

IExternalEventHandler execution can’t be nested in Revit.

Unfortunately there is no way to temporarily interrupt and IExternalEventHandler and return control back to Revit.
The only way to make Revit executes next IExternalEventHandler on the queue is to finish the current one.

Again asynchronous does not mean simultaneous, but interleaved.

In this case Grasshopper is synchronous, it needs to finish solving all the components in the canvas before returning control back. So without a big reaquitecture of Grasshopper this can’t be done.

@thomas7,

A workaround may be running those tasks synchronous when called from Grasshopper.
If you are using Revit.Async something like this should work (NOT TESTED :wink:).

namespace Bimorph
{
  using RhinoInside.Revit;

  public class BimorphTask
  {
    private static Autodesk.Revit.UI.UIApplication Application = null;

    public static System.Threading.Tasks.Task<T> Run<T>(Func<Autodesk.Revit.UI.UIApplication, T> action)
    {
      if (Application == null)
        return RevitTask.RunAsync(action);
      else
      {
        var task = new System.Threading.Tasks.Task<T>(() => action(Application));
        task.RunSynchronously();
        return task;
      }
    }

    class SynchronousScopeDisposable : IDisposable
    {
      readonly Autodesk.Revit.UI.UIApplication Application;
      public SynchronousScopeDisposable(Autodesk.Revit.UI.UIApplication app)
      {
        if (app == null)
          throw new ArgumentNullException(nameof(app));

        Application = BimorphTask.Application;
        BimorphTask.Application = app;
      }
      void IDisposable.Dispose()
      {
        BimorphTask.Application = Application;
      }
    }

    public static IDisposable SynchronousScope(Autodesk.Revit.UI.UIApplication app) => new SynchronousScopeDisposable(app);
  }

  public class BimorphComponent : GH_Component
  {
    protected override async void SolveInstance(IGH_DataAccess DA)
    {
      using (BimorphTask.SynchronousScope(Revit.ActiveUIApplication))
      {
        // You can call BimorphTask.Run here or any method that ends up calling BimorphTask.Run
        var count = await BimorphTask.Run
        (
          app =>
          {
            using (var collector = new Autodesk.Revit.DB.FilteredElementCollector(app.ActiveUIDocument.Document).OfClass(typeof(Autodesk.Revit.DB.Material)))
              return collector.GetElementCount();
          }
        );

        DA.SetData("Materials Count", count);
      }
    }
  }
}
```

Thanks @kike its appreciated. We’re using our own async wrapper around the implementation of the IExternalEventHandler for code brevity reasons. I tried running the code synchronously but we cant use Task.RunSynchronously since the methods we’re calling use the async modifier with a parameterless Task return type which prohibits the use of RunSynchronously. Its a catch 22!

I’m going to explore the route of running the start up services via a debug app in Revit with a singleton for access in GH as preserving the natural initialization of the app is critical for our debugging purposes.