is there a better way to handle RPC_E_CALL_REJECTED exceptions when doing visual studio automation?

5

this is what I'm currently doing:

    protected void setupProject()
    {
        bool lbDone = false;
        int liCount = 0;
        while (!lbDone && liCount < pMaxRetries)
        {
            try
            {
                pProject.ProjectItems.Item("Class1.cs").Delete();
                lbDone = true;
            }
            catch (System.Runtime.InteropServices.COMException loE)
            {
                liCount++;
                if ((uint)loE.ErrorCode == 0x80010001)
                {
                    // RPC_E_CALL_REJECTED - sleep half sec then try again
                    System.Threading.Thread.Sleep(pDelayBetweenRetry);
                }
            }
        }
    }

now I have that try catch block around most calls to the EnvDTE stuff, and it works well enough. The problem I have is when I to loop through a collection and do something to each item once.

foreach(ProjectItem pi in pProject.ProjectItems)
{
    // do something to pi
}

Sometimes I get the exception in the foreach(ProjectItem pi in pProject.ProjectItems) line. Since I don't want to start the foreach loop over if I get the RPC_E_CALL_REJECTED exception I'm not sure what I can do.

Edit to answer comment: Yes I'm automating VS from another program and yes I usually am using VS for something else at the same time. We have an application that reads an xml file then generates around 50 VS solutions based on the xml file. This usually takes a couple of hours so I try to do other work while this is happening.

c#
visual-studio-2010
code-generation
asked on Stack Overflow Mar 16, 2011 by scott • edited Mar 16, 2011 by scott

4 Answers

3

There is a solution on this MSDN page: How to: Fix 'Application is Busy' and 'Call was Rejected By Callee' Errors. It shows how to implement a COM IOleMessageFilter interface so that it will automatically retry the call.

answered on Stack Overflow Jun 6, 2011 by Daniel Plaisted
1

First, Hans doesn't want to say so but the best answer to "how to do this" is "don't do this". Just use separate instances of visual studio for your automation and your other work, if at all possible.

You need to take your problem statement out somewhere you can handle the error. You can do this by using in integer index instead of foreach.

// You might also need try/catch for this!
int cProjectItems = pProject.ProjectItems.Length;
for(iProjectItem = 0; iProjectItem < cProjectItems; iProjectItem++)
{
   bool bSucceeded = false;
   while(!bSucceeded)
   {
        try{
            ProjectItem pi = pProject.ProjectItems[iProjectItem];
            // do something with pi
            bSucceeded = true;
        }catch (System.Runtime.InteropServices.COMException loE)
        {
            liCount++;
            if ((uint)loE.ErrorCode == 0x80010001)                      {
                // RPC_E_CALL_REJECTED - sleep half sec then try again
                System.Threading.Thread.Sleep(pDelayBetweenRetry);
            }
        }  
   }

}
answered on Stack Overflow Mar 16, 2011 by Ben
1

I didn't have much luck with the recommended way from MSDN, and it seemed rather complicated. What I have done is to wrap up the re-try logic, rather like in the original post, into a generic utility function. You call it like this:

Projects projects = Utils.call( () => (m_dteSolution.Projects) );

The 'call' function calls the function (passed in as a lambda expression) and will retry if necessary. Because it is a generic function, you can use it to call any EnvDTE properties or methods, and it will return the correct type.

Here's the code for the function:

public static T call<T>(Func<T> fn)
{
    // We will try to call the function up to 100 times...
    for (int i=0; i<100; ++i)
    {
        try
        {
            // We call the function passed in and return the result...
            return fn();
        }
        catch (COMException)
        {
            // We've caught a COM exception, which is most likely
            // a Server is Busy exception. So we sleep for a short
            // while, and then try again...
            Thread.Sleep(1);
        }
    }
    throw new Exception("'call' failed to call function after 100 tries.");
}

As the original post says, foreach over EnvDTE collections can be a problem as there are implicit calls during the looping. So I use my 'call' function to get the Count proprty and then iterate using an index. It's uglier than foreach, but the 'call' function makes it not so bad, as there aren't so many try...catches around. For example:

int numProjects = Utils.call(() => (projects.Count));
for (int i = 1; i <= numProjects; ++i)
{
    Project project = Utils.call(() => (projects.Item(i)));
    parseProject(project);
}
answered on Stack Overflow Dec 19, 2011 by Richard Shepherd
0

I was getting the same error using C# to read/write to Excel. Oddly, it worked in debug mode but not on a deployed machine. I simply changed the Excel app to be Visible, and it works properly, albeit about twice as slow. It is annoying to have an Excel app open and close dynamically on your screen, but this seems to be the simplest work-around for Excel.

Microsoft.Office.Interop.Excel.Application oApp = new ApplicationClass();
oApp.Visible = true;
oApp.DisplayAlerts = false;
answered on Stack Overflow Nov 11, 2015 by John

User contributions licensed under CC BY-SA 3.0