KennanChan / Revit.Async

Use task-based asynchronous pattern (TAP) to run Revit API code from any execution context.
MIT License
223 stars 51 forks source link

Sometimes InvalidOperationException: 'Starting a transaction from an external application..." #23

Closed RyanPatrickDaley closed 1 year ago

RyanPatrickDaley commented 1 year ago

When I use RevitTast.RunAsync(), it seems to work most of the time. However, I occasionally still get the InvalidOperationException sometimes for running a method outside of the API context. It doesn't make any sense to me that it would only fail sometimes. Maybe its because I am trying to run it consecutively, and the thread is already busy. Maybe its because I have multiple transactions within the same RevitTask.RunAsync. Maybe its because it doesn't like running static methods. I'm not sure what the issue could be.

 private async void Replace()
        {
                await RevitTask.RunAsync(app =>
                {
                    Document doc = app.ActiveUIDocument.Document;
                    foreach (ElementId id in SelectedFam.ElementIds)
                    {
                        Element ele = doc.GetElement(id);
                        if (ele == null)
                            throw new NullReferenceException();
                        HostObject host;
                        XYZ placementLocation;
                        Level level;
                        FamilyInstance fam = ele as FamilyInstance;
                        if (fam != null)
                        {
                            host = fam.Host as HostObject;
                            level = doc.GetElement(fam.LevelId) as Level;
                            placementLocation = ((LocationPoint)fam.Location).Point;

                            ElementId typeId = ele.GetTypeId();
                            ElementType type = doc.GetElement(typeId) as ElementType;
                            if (type == null)
                                throw new NullReferenceException("Can not find the element type");
                            using (Transaction tx = new Transaction(doc, "Delete Family"))
                            {
                                tx.Start("Delete Family");
                                doc.Delete(id);
                                tx.Commit();
                                tx.Dispose();
                            }
                            try
                            {
                                Methods.PlaceEntityInKnownLocation(doc, app, SelectedReplacement, host, placementLocation, level);
                            }
                            catch (Exception ex)
                            {
                                SimpleLog.Log(ex);
                            }
                });

 public static async void PlaceEntityInKnownLocation(Document doc, UIApplication uiApp, EntityModels.Material entity, Element host, XYZ placementLocation, Level level)
        {

            //Get application and document objects

            Autodesk.Revit.ApplicationServices.Application app = uiApp.Application;

            //IT DOESN'T ALREADY EXIST,
            //CHECK FILE TO LOAD IT FROM
            string dir = System.IO.Path.GetDirectoryName(
      System.Reflection.Assembly.GetExecutingAssembly().Location);
            string localSaveLocation = dir + $"\\{entity.Name}.rfa";

            if (!File.Exists(localSaveLocation))
            {
                //DownloadWindow
                await HttpClientInstance.DownloadFamilyAsync(entity);
            }

            //SEE IF THE ELEMENT ALREADY EXISTS IN THE PROJECT
            FilteredElementCollector a
                = new FilteredElementCollector(doc)
            .OfClass(typeof(Family));

            Family family = a.FirstOrDefault<Element>(
                e => e.Name.Equals(entity.Name))
                as Family;
            FamilySymbol symbol = null;

            // LOAD THE FAMILY INTO THE PROJECT
            using (Transaction tx = new Transaction(doc, "Load Family"))
            {
                tx.Start("Load Family");
                if (family == null)
                {
                    bool res = doc.LoadFamily(localSaveLocation, out family);
                }
                if (family != null)
                {
                    //DETERMINE THE FAMILY SYMBOL
                    ISet<ElementId> symbolIds = family.GetFamilySymbolIds();
                    foreach (ElementId symbolId in symbolIds)
                    {
                        symbol = doc.GetElement(symbolId) as FamilySymbol;
                        if(symbol != null)
                        {
                            if (!symbol.IsActive)
                                symbol.Activate();
                            break;
                        }
                    }
                }
                tx.Commit();
            }

            if (symbol != null)
            {
                using (Transaction tx = new Transaction(doc, "Place Family"))
                {
                    tx.Start("Place Family");
                    try
                    {
                        doc.Create.NewFamilyInstance(placementLocation, symbol, host, level, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);

                    }
                    catch (Autodesk.Revit.Exceptions.OperationCanceledException ex)
                    {
                        SimpleLog.Log(ex);
                    }
                    tx.Commit();
                }
            } 
            return;
        }

image

KennanChan commented 1 year ago

I noticed that you tried to call revit api after an http request returned. I believe that's the problem. (the "Download Window" comment) Your code after http request will be executed in a new thread. I suggest you to wrap these code in RevitTask.RunAsync again. It is ok to nest RevitTask calls.

KennanChan commented 1 year ago

Let me know if the issue had been resolved~