[CONNECT C#] how do I use a background thread in my addon?

I'm trying to use a background worker in my wpf c# addon app. but I'm getting an System.Access.ViolationException error. on the .net API calls. I know its because its in a new thread. What do I have to do in order to have access to the .net api in a new thread? (note when I move the code back into the UI thread it works fine, so I know the issue is with the thread.)

thanks

JD

Parents
  • Can you be specific about what .NET API calls you are making?

    If they are APIs supplied by the MicroStation SDK, the answer is that the MicroStation SDK in general is not designed for use in background threads.

  • the application is going to be going through a lot of dgn files and I just wanted to keep the window responsive. and have a progress bar. for stand alone apps I always just used a background worker or created a new thread. So maybe there is a better way to do this.

    but here is some of the initial code in the new thread...

    BD.DgnDocument dgndoc = BD.DgnDocument.CreateForLocalFile(testFilePath);
    BD.DgnFileOwner fileowner = BD.DgnFile.Create(dgndoc, BD.DgnFileOpenMode.ReadWrite);
    
    BD.DgnFile curfile = fileowner.DgnFile;
    BD.StatusInt openstatus;
    curfile.LoadDgnFile(out openstatus);

    JD

  • Hi John,

    my experience is limited, but I do not see a reason to use multithreading here and in my opinion the approach you use is not possible.

    the application is going to be going through a lot of dgn files

    It has to be done at your addin code, not in another thread.

    and I just wanted to keep the window responsive

    I am not GUI expert, but my understanding of current best practices is that to keep GUI responsibe it to use async/await, not threads.

    To start a new thread from a pool (or even worse, to start a new process, which is pretty expensive) makes sense when long term activity, not related to MicroStation API, is expected. E.g. to communicate with web server / service or to stream data from file or database. Such thread cannot communicate directly with MicroStation API, but has return data (Task<> ?) back to parent code (addin) which will takes care about using MicroStation API.

    for stand alone apps I always just used a background worker or created a new thread

    There is nothing bad with background worker, it's pre-baked solution that works well. But my experience is that often something more would be nice to have and TPL provides plenty of very nice features.

    When talking about not standalone applicaiton but MicroStation addin, the usage of threads are limited when the code is mostly accessing MicroStation API.

    So maybe there is a better way to do this.

    When talking about GUI, check async/await (which is mandatory knowledge for C# developers today anyway ;-)

    There are plenty of blogs, articles and tutorials about async/awit. Last week I attended Update Conference in Prague (btw great event!) and one presentation was about mistakes in async/await usage. Notes from this guy (they are advanced materials) can be found here.

    With regards,

      Jan

    Answer Verified By: John Drsek 

  • Thanks Jan,

    You always seem to steer me in the right direction. I have created my own threads and dispatched into the GUI to update things in the past(I don't always use the background worker). So it shouldn't be to hard to use async and dispatch in to update the progress bar.

    thanks

    JD

  • Jan,

    It appears async/await still will not work in keeping the window responsive.

    I'm not positive but I think the apis in general are blocking calls. I'm looping through a list of dgn files and then looping through all the models and finding any ItemTypes with the a certain name. I use async/await and my mainwindow is responsive for all the code except when it reads through the dgns. im at a loss for how to get around this.

    here is how I implemented it...on loaded..

    private async void Window_Loaded(object sender, RoutedEventArgs e)
            {
                //show loading
                s_window.Dispatcher.Invoke((Action)delegate
                {
                    s_window.loadingFrame.Content = curloadingpage;
                    txtProgress.Text = "Loading...";
                });
                bool result = false;
                Storyboard sb = (Storyboard)curloadingpage.FindResource("Storyboard1");
                sb.Begin(); //begin storyboard loading annimation
                //..
                ..code to get list of files...
                //..
                result = await Methods.processModels();
                if (result == true)
                {//update things in UI
                    s_window.txtWorkSetDir.Text = Globals.PIDFolderPath;
                    s_window.cboxMode.SelectedIndex = 0;
                    s_window.loadingFrame.Visibility = Visibility.Hidden;
                }
                txtProgress.Text = Globals.strResult;
                sb.Stop();
            }

    which calls this

    public async static Task<bool> processModels()
    {
        try
        {
            bool result;
            //loop through the initial list of dgn files and get model info and any itemtypes info
            foreach (InitialFile curFile in Globals.IntitialFileList)
            {
                result = await getModelInfo(curFile);
                if (result == false)
                {//something went wrong..list this file in results for not being able to read its info
                    Globals.strResultsList = Globals.strResultsList + "Failed to read Item Types from " + curFile.filepath + ", file omitted!";
                }
            }
            return true;
        }
        catch (Exception e)
        {
            Exception error = e;
            return false;
        }
    }

    and then calls this within the loop..here is where the UI becomes unresponsive. should I be loading the dgn differently??

    public async static Task<bool> getModelInfo(InitialFile curinitialFile)
            {
                await Task.Delay(1); // need await to make async happy
                BD.DgnFile curfile = null;
                Bentley.DgnPlatformNET.ElementId eleID = new Bentley.DgnPlatformNET.ElementId();
                string curmodelname = "";
                try
                {
                    bool result = true;
                    //do main work 
                    //open file
                    BD.DgnDocument dgndoc = BD.DgnDocument.CreateForLocalFile(curinitialFile.localpath);
                    BD.DgnFileOwner fileowner = BD.DgnFile.Create(dgndoc, BD.DgnFileOpenMode.ReadOnly);
                    curfile = fileowner.DgnFile;
                    BD.StatusInt openstatus;
                    curfile.LoadDgnFile(out openstatus);
                    if (openstatus != BD.StatusInt.Success || curfile.IsLoaded == false)
                    {//something went wrong, cant load file
                        result = false;
                    }
                    else
                    {
                        BD.ModelIndexCollection collection = curfile.GetModelIndexCollection();
                        IEnumerator<BD.ModelIndex> models = collection.GetEnumerator();
                        //loop each model in file
                        while (models.MoveNext())
                        {
                            int counter = 0;
                            BD.DgnEC.IDgnECInstance foundItemType = null;
                            BD.StatusInt status;
                            BD.DgnModel model = curfile.LoadRootModelById(out status, models.Current.Id);
                            BD.ModelInfo curInfo = model.GetModelInfo();
                            //only seach sheet model types
                            if (curInfo.ModelType == BD.DgnModelType.Sheet)
                            {//need to search all elements 
                                counter = 0;
                                curmodelname = curInfo.Name;
                                BD.ModelElementsCollection eleCol = model.GetGraphicElements();
                                foreach (Element curEle in eleCol)
                                {
                                    if (counter > 1)
                                    {//stop search if more then one is found
                                        break;
                                    }
                                    if (curEle.ElementType == Bentley.DgnPlatformNET.MSElementType.CellHeader)
                                    {
                                        foreach (Element subEle in curEle.GetChildren())
                                        {//loop sub elements
                                            if (counter > 1)
                                            {//stop search if more then one is found
                                                break;
                                            }
                                            Bentley.DgnPlatformNET.CustomItemHost subhost = new Bentley.DgnPlatformNET.CustomItemHost(subEle, true);
                                            IList<BD.DgnEC.IDgnECInstance> subItemTypes = subhost.CustomItems;
                                            foreach (BD.DgnEC.IDgnECInstance curItemType in subItemTypes)
                                            {
                                                if (curItemType.ClassDefinition.Name.ToUpper().Contains("OHDOT_SHEET"))
                                                {//found 
                                                    counter = counter + 1;
                                                    if (counter > 1)
                                                    {//more then one found ignore model
                                                        break;
                                                    }
                                                    else
                                                    {
                                                        foundItemType = curItemType;
                                                        eleID = subEle.ElementId;
                                                    }
                                                }
                                            } //next item type
                                        }//next subelement
                                    }
                                    else
                                    {//not cell header
                                        Bentley.DgnPlatformNET.CustomItemHost host = new Bentley.DgnPlatformNET.CustomItemHost(curEle, true);
                                        IList<BD.DgnEC.IDgnECInstance> ItemTypes = host.CustomItems;
                                        foreach (BD.DgnEC.IDgnECInstance curItemType in ItemTypes)
                                        {
                                            if (curItemType.ClassDefinition.Name.ToUpper().Contains("OHDOT_SHEET"))
                                            {//found 
                                                counter = counter + 1;
                                                if (counter > 1)
                                                {//more then one itemtype found ignore model
                                                    break;
                                                }
                                                else
                                                {
                                                    foundItemType = curItemType;
                                                    eleID = curEle.ElementId;
                                                }
                                            }
                                        }//next itemtype
                                    }
                                }//next element
                            }//done searching model
    
                            //add new sheet if only one itemtype was found in model
                            if (counter == 1)
                            {
                                Sheet newSheet = new Sheet();
                                List<Field> fieldsinsheet = new List<Field>();
                                newSheet.propDiscipline = curinitialFile.discipline;
                                newSheet.propFileID = curinitialFile.fileID;
                                newSheet.propFileName = Path.GetFileName(curinitialFile.localpath);
                                newSheet.propFilePath = curinitialFile.filepath;
                                newSheet.propFolder = curinitialFile.engfolder; //engineering folder
                                newSheet.propFolderID = curinitialFile.folderID;
                                newSheet.propLocked = false;
                                newSheet.propModelName = curmodelname;
                                newSheet.propSheetName = "";
                                newSheet.propModelID = models.Current.Id;
                                newSheet.propEleID = eleID;
                                //newSheet.propSheetNumber = "";
                                newSheet.propShowinWindow = true;
    
                                foreach (var curprop in foundItemType)
                                {
                                    Field newField = new Field();
                                    newField.propDataTemplate = "";
                                    newField.propFieldName = curprop.Property.DisplayLabel;
                                    newField.propFieldType = curprop.Type;
                                    newField.propFieldValue = curprop.StringValue;
                                    newField.propLocked = false;
                                    newField.propShowinWindow = true;
                                    if (newField.propFieldName.ToUpper() == "SHEET NUMBER")
                                    {
                                        newSheet.propSheetNumber = newField.propFieldValue;
                                    }
                                    fieldsinsheet.Add(newField);
                                }
                                newSheet.propFields = fieldsinsheet;
                                Globals.SheetList.Add(newSheet);
                            }
                        }//next model
                        //close dgnfile
                        curfile.Release();
                        result = true;
                    }
                    if (result == false)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
                catch (Exception e)
                {
                    Exception error = e;
                    if (curfile == null) { }
                    else
                    {
                        try
                        {
                            curfile.Release();
                        }
                        catch (Exception)
                        {
                            return false;
                        }
                    }
                    return false;
                }
            }

    any ideas anyone??

  • Hi John,

    I have not enough time to think about it, but...

    except when it reads through the dgns

    It makes sense and I think it's not surprise: Addin GUI runs in the same (main) thread as MicroStation, so when MicroStation does something, GUI update is on hold. But my experience is that the reading of data is pretty quick and does not block GUI from user perspective. Typical problem of many applications is they do everything is some nested cycles (they steal the thread) and does not allow MicroStation to do anything else.

    But it would require some extra time to play with it ... but where to find one or two free days? ;-)

    Maybe somebody from Bentley would provide more details how addin loader is implemented and how async/await works?

    any ideas anyone??

    I do not consider myself async/await expert, but to have in code await Task.Delay(1); // need await to make async happy is in my opinion a proof something is wrong. No code can rely on waiting.

    Regards,

      Jan

  • maybe there is a better way to do this

    I think you're making this over complex.  If your requirement is to make the app. seem responsive, and provide user feedback, while chugging through a list of DGN files, why not use a Progress Bar?

    Here's an article about the Progress Bar class.

     
    Regards, Jon Summers
    LA Solutions

Reply Children
No Data