Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Can you chain Idling events?

22 REPLIES 22
Reply
Message 1 of 23
ariWZRFQ
890 Views, 22 Replies

Can you chain Idling events?

Is it possible to chain Idling events?

 

Say I have two functions, FuncA and FuncB. I want to run FuncA the first time revit idles, then run FuncB after FuncA is done running and revit idles again. Inside my FuncA handler, I am removing the handler to FuncA from UIApp.Idling, but I can not assign a new handler because I don't have a valid API context.

 

Is there a way for me to achieve my intended behavior?

 

NOTE: The reason I'm doing any of this at all is because of this referenced post. For some reason, Revit throws an internal exception if my FuncA and FuncB are called one after the other. So I am separating them with an idling event. Now I need to add another idling event before FuncA.

Tags (1)
22 REPLIES 22
Message 2 of 23
RPTHOMAS108
in reply to: ariWZRFQ

It doesn't sound like a good idea in general, however.

 

When Revit idles you want to run FunctionA when it idles again you want to run FunctionA but with different settings i.e. subscribe to FunctionA and change what FunctionA does based on what it did last. Then after a set number of things that FunctionA does unsubscribe. In the end you are waiting for the same event in-between doing tasks with the same function.

 

You can use static variable etc. to store stage of FunctionA.

 

 

Message 3 of 23
jeremy_tammik
in reply to: ariWZRFQ

Afaik, you cannot chain them directly, but you can easily achieve the same effect:

 

  • Subscribe to the Idling event and attach handlerA
  • In handlerA, unsubscribe from the Idling event, then subscribe to the Idling event and attach handlerB

  

Depending on other factors, Revit may or may not execute other actions in between the two event handler calls, but their relative order should be preserved.

  

You say, I cannot assign a new handler because I don't have a valid API context.

  

Why not? Within the Idling event handler, I thought you do have a valid Revit API context.

  

Question: why do the actions A and B have to be executed separately? Can't you just execute them both within the same Idling event handler?

  

Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open
Message 4 of 23
ricaun
in reply to: ariWZRFQ

You should create a queue service for that, subscribing and unsubscribing Revit events in your App as static.

 

Something like this maybe helps: https://gist.github.com/ricaun/11b272c5bec46f05c3fe49525fcc3fdf

 

You can register the service in your IExternalApplication.

public class App : IExternalApplication
{
    public static IdlingQueueService IdlingActionService { get; private set; }
    public Result OnStartup(UIControlledApplication application)
    {
        IdlingActionService = new IdlingQueueService(application);
        return Result.Succeeded;
    }

    public Result OnShutdown(UIControlledApplication application)
    {
        IdlingActionService?.Dispose();
        return Result.Succeeded;
    }
}

 

Then you could add some Action to run on the Idling Event, each one gonna run in a different Idling Event.

[Transaction(TransactionMode.Manual)]
public class Command : IExternalCommand
{
    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elementSet)
    {
        App.IdlingActionService.Add((uiapp) => { System.Windows.MessageBox.Show("1"); });
        App.IdlingActionService.Add((uiapp) => { System.Windows.MessageBox.Show("2"); });

        return Result.Succeeded;
    }
}

 

I hope this helps.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 5 of 23
ariWZRFQ
in reply to: ariWZRFQ

Thank you all for your responses. I have tried 3 different solutions all to no success.

 

  • I tried running 1 idling event with block if statements just to perform different actions based on some state variables.
    • This fails because one of my functions also ends up giving Revit the opportunity to idle. This causes a never ending idling loop.
  • I retried subscribing to a new handler inside of handler A.
    • This fails with an error "Cannot subscribe to an event during execution of that event". I am attempting to subscribe to a different handler function with code in the form of below.
    •  

 

 

public async void OnIdlingA(object sender, IdlingEventArgs e)
{
     Autodesk.Revit.UI.UIApplication uiapp = sender as 
     Autodesk.Revit.UI.UIApplication;


     Debug.Assert(null != uiapp,
         "expected a valid Revit application instance");

     if (uiapp != null)
     {
          uiapp.Idling -= OnIdlingA;
          uiapp.Idling += new
                EventHandler<Autodesk.Revit.UI.Events.IdlingEventArgs>
                (OnIdlingB);

          await FuncA();
     }
}
​

 

 

  • Finally, I tried the Idling Queue service
    • This also fails due the the fact that I am getting extra idling events while my functions are running causing the execution to dive into the idling functions before finishing the current function.

 

 

If I step back from all the details of my implementation, my problem is really not too complicated. I just wish I could run the Execute function of IExternalCommand like below.

 

 

 

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
      Object1 obj1 = new Object1(commandData)
      
      await obj1.func1;
      await obj1.func2;
      obj1.func3;  //THIS IS THE ONLY REVIT INTERACTION

      return Autodesk.Revit.UI.Result.Succeeded;
}

 

 

 

This doesn't work because I can not make the Execute function async but this is the theory. Not being able to await func1 and func2 is what is causing me to dive so deep into idling events. Func1 and Func2 have nothing to do with Revit. 

 

Func1 calls a web server to do authentication in a webview

Func2 calls a web server to download Revit files

Func3 opens those files and uses that data to manipulate the current document.

 

Thank you all for the guidance

Message 6 of 23
ricaun
in reply to: ariWZRFQ

You forgot to explain that you are using 'await' functions.

 

Revit does not like async stuff, and your code looks odd. How can you add an await without an async method...

 

To work with async and Revit use the Revit.Async package, so the package gonna manage the Idling stuff.

 

 

 

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 7 of 23
ariWZRFQ
in reply to: ricaun

Sorry, I just edited my reply for clarity. I am not actually running the functions with await as shown. That is how I would do it if the Execute function could be made async.

Func1 and Func2 are async functions which is why I am/was trying to run them in idling events.

Message 8 of 23
ricaun
in reply to: ariWZRFQ

One thing that is important is that when an IExternalCommand is running the Idling is not gonna trigger until the command ends. The Idling means the Revit is not busy doing something and is ready to receive some user interaction.

 

I guess the best approach in your case should be to use Revit.Async package with something like this.

 

 

public async Task Execute()
{
      await func1;
      await func2;
      await RevitTask.RunAsync(func3);  //THIS IS THE ONLY REVIT INTERACTION
}

 

 

Using the RevitTask you can run the func3 in Revit context.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 9 of 23
jeremy_tammik
in reply to: ariWZRFQ

One correct, reliable and effective approach to address the situation you describe is to implement an external event:

 

https://thebuildingcoder.typepad.com/blog/about-the-author.html#5.28

  

In the event handler, simply run the code to execute func3.

  

All the rest has nothing to do with Revit and does not require the Revit API.

  

Therefore, you can execute it independently, externally, and simply raise the external event after func1 and func2 have completed successfully.

  

Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open
Message 10 of 23
jeremy_tammik
in reply to: ariWZRFQ

Oh dear. I just noticed your statement:

  

Func1 and Func2 are async functions which is why I am/was trying to run them in idling events.

  

Well, that is just about the opposite of what you could or should do. Or at least it is not intended, afaict.

  

Revit and its API is single threaded. The Idling event handler is single threaded. So, as long as that event handler is busy executing, e.g., your func1 and func2, it will block Revit from doing anything else.

  

The purpose of the Idling event is to enable you to execute some action as soon as Revit has stopped being occupied with and therefore blocked by other tasks.

  

This confirms my preceding answer: it seems to me that an appropriate way to handle this would be to disconnect func1 and func2 from Revit and your add-in as much as possible, and let them trigger an external event when they are completed, to execute func3.

  
Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open
Message 11 of 23
ariWZRFQ
in reply to: ariWZRFQ

 

So what I ended up doing is below. It seems to work but would love some feedback if its unsafe:

  • The main IExternalCommand Execute function just subscribes to an idling event.
  • The event handler is an async function.
  • In the async event handler function, I await the 3 functions and wrap the third one with RevitTask.

 

This still feels like the original idling action is unnecessary and I should be able to just dive right into the async func1, but other than that this works!

 

@jeremy_tammik How would you recommend disconnecting Func1 and Func2? The functions are async in that they use async functions in them but I don't need them to run truly asynchronously. Blocking Revit is fine for them. At all points in my code base, I am awaiting the results of the async functions so that everything runs essentially synchronously. All of my problems are due to the fact that the Revit execute function can not be made "async" so I can not "await" func1 and func2. 

 

I can't imagine that this is the first time this problem has been encountered. It's a pretty standard workflow assuming that other plugins need some sort of authentication. First send an HTTP request to a server, then following a successful response, do all the Revit stuff.

Message 12 of 23
jeremy_tammik
in reply to: ariWZRFQ

Yes, this is a standard situation, and I described the standard solution above. No need for async anything, just create an external event X, raise the event when func1 and func2 have completed, and execute func3 in the external event X handler. No Idling needed, no async needed.

     

Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open
Message 13 of 23
ariWZRFQ
in reply to: ariWZRFQ

Oh maybe I need to learn more about how async works in C# if its different than JS development.

 

I would expect that if run func1 and func2 without the await flag, func2 will start before func1 has completed and the external event will be raised before func1 and func2 are completed. This would not work for my code since I would be trying to run code before the authentication step (func1) is completed.

 

Have I misunderstood? Thanks

Message 14 of 23
jeremy_tammik
in reply to: ariWZRFQ

In the context of the Revit. API, I would recommend scrapping async completely. That will totally simplify things.

  

Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open
Message 15 of 23
ariWZRFQ
in reply to: ariWZRFQ

haha I wish, but I still need to send requests to external servers. I can't think of a way to do that without async.

Message 16 of 23
jeremy_tammik
in reply to: ariWZRFQ

Well, I did myself, many times, connecting desktop and cloud:

  

https://github.com/jeremytammik/FireRatingCloud

  

Not very long ago, async did not exist. Push and pull functionality was up and running for decades before async was invented in the form you know it:

  

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history

  

  

Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open
Message 17 of 23
Kennan.Chen
in reply to: ariWZRFQ

As @ricaun has mentioned, it seems to be a highly matched case that Revit.Async is designed to solve.

 

The first time I came up with the idea of Revit.Async was in a cloud rfa management system. The main use case was to query the server about the information of an rfa file (your function 1), then download it (your function 2) and finally place it with Revit API (your function 3). I used a pattern similar to Revit.Async to chain them up and it worked like a charm.

 

I would suggest you to take a look at Revit.Async and you will not be disappointed.

Message 18 of 23
jeremy_tammik
in reply to: ariWZRFQ

Many thanks to everybody for the interesting discussion and valuable contributions, especially to Kennan for implementing and sharing Revit.Async. I summarised the discussion for posterity on the blog:

  

https://thebuildingcoder.typepad.com/blog/2022/10/can-you-avoid-chaining-idling.html#6

  

Jeremy Tammik, Developer Advocacy and Support, The Building Coder, Autodesk Developer Network, ADN Open
Message 19 of 23
ariWZRFQ
in reply to: ariWZRFQ

Thank you all for the suggestions and help!

 

Thank you @Kennan.Chen, I have Revit.Async in my code base but I don't think I am using it most effectively. Can you explain what you used for your original use case (so the same structure I would use)?

 

The part that I can't connect is how to enter the async function from the main IExternalCommands Execute function. The below is what I would have thought but I wasn't able to get this to work before so I had to instead add the runMyFunctions() as an idling event handler and then it worked.

 

 

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            runMyFunctions();
            return Autodesk.Revit.UI.Result.Succeeded;
        }

public async void runMyFunctions()
        {
            await func1();
            await func2();
            await RevitTask.RunAsync(uiapp2 => {func3();});
        }

 

 

Again, thank you all for the great help and discussion. Really appreciate everyones insight and it has helped me along immensely.

Message 20 of 23
Kennan.Chen
in reply to: ariWZRFQ

You need to call RevitTask.Initialize(app) in a valid Revit API context to get Revit.Async ready to use.

 

Some valid Revit API contexts are:

IExternalCommand.Execute method

IExternalApplication.OnStartup method

IExternalEventHandler.Execute method

Revit API event handlers

IUpdater

 

In your case, you can initialize Revit.Async in IExternalCommand.Execute method.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Rail Community