简体   繁体   English

不知道为什么C#PowerShell Runspace不定期关闭

[英]Don't know why C# PowerShell Runspace is closing irregularly

I have a MVC web application which shows some information about users in our AD. 我有一个MVC Web应用程序,其中显示了有关我们AD中用户的一些信息。 The AD is synchronized with Office 365, so using the UPN I can retrieve the license information from Office 365 using the Windows PowerShell cmdlets for Office 365 . AD与Office 365同步,因此使用UPN可以使用Office 365的Windows PowerShell cmdlet从Office 365检索许可证信息。 Basically this all works fine. 基本上,这一切都很好。

As the initialization cmdlet Connect-MsolService takes some time to finish, I'm using kind of a singleton pattern for my Office365Connector class. 由于初始化cmdlet Connect-MsolService需要一些时间才能完成,因此我在Office365Connector类中使用了一种单例模式。 In my Global.asax in Application_Start() I initialize the singleton instance, in Application_End() I dispose it. Application_Start() Global.asax中,我初始化单例实例,在Application_End() ,将其处置。 The connector class uses exactly one instance of my PowerShellInvoker class which - as the name implies - encapsulates PowerShell invoking. 连接器类恰好使用了我的PowerShellInvoker类的一个实例,顾名思义,该实例封装了PowerShell调用。 The PowerShell initialization code inside the PowerShellInvoker constructor looks like this: PowerShellInvoker构造函数中的PowerShell初始化代码如下所示:

public PowerShellInvoker(params string[] modules)
{
    var iss = InitialSessionState.CreateDefault();
    iss.ImportPSModule(modules);
    iss.ThrowOnRunspaceOpenError = true;

    _runspace = RunspaceFactory.CreateRunspace(iss);
    _runspace.Open();
    _invoker = new RunspaceInvoke(_runspace);
}

The Office365Connector class calls this constructor with "MSOnline" as parameter. Office365Connector类使用"MSOnline"作为参数调用此构造函数。 The MSOnline module contains the cmdlets for Office 365. I keep the _runspace and _invoker fields for command execution at a later time. MSOnline模块包含Office 365的cmdlet。我保留_runspace_invoker字段以供以后执行命令。 Both fields will be disposed in the Dispose method of PowerShellInvoker (which is called when the Office365Connector class is being disposed). 这两个字段都将在PowerShellInvokerDispose方法中进行处置(在Dispose Office365Connector类时会调用此Office365Connector )。 Script execution is done by this line of code: 脚本执行通过以下代码行完成:

_invoker.Invoke(scriptText);

So much for the introduction - now here comes the real problem: 这么多的介绍-现在是真正的问题:

In my application, I have a user list. 在我的应用程序中,我有一个用户列表。 When I click a user, additional information is loaded using an AJAX request. 当我单击用户时,将使用AJAX请求加载其他信息。 Within this request, my app uses the singleton instance of the Office365Connector class to retrieve the license information for the user. 在此请求中,我的应用程序使用Office365Connector类的单例实例来检索用户的许可证信息。 In most cases, this all works perfectly. 在大多数情况下,这一切都很好。 But sometimes the AJAX request ends up with a code 500. Debugging my source code, I stumbled upon an Exception being thrown in the PowerShellInvoker on the "Invoke" line above, telling me that the Runspace is not open anymore, and I can't figure out why. 但是有时AJAX请求最终以代码500结束。在调试我的源代码时,我偶然发现PowerShellInvoker在上面“调用”行上引发了异常,告诉我运行空间不再打开,并且我不能找出原因。 I can't even really reproduce it. 我什至无法真正复制它。 Sometimes, the error occurs when I click the second user. 有时,当我单击第二个用户时,会发生错误。 Sometimes, the error occurs on the 10th or 15th user. 有时,错误发生在第10位或第15位用户上。 I already thought about some weird clean-up, timeout or garbage collection techniques used by MVC, but I haven't come to a conclusion. 我已经考虑过MVC使用的一些怪异的清理,超时或垃圾收集技术,但是我尚未得出结论。 IMHO, the Runspace closing can't be time-based because the time between the "user clicks" is just a few seconds. 恕我直言,运行空间的关闭不能基于时间,因为“用户单击”之间的时间只有几秒钟。

The Connect-MsolService cmdlet creates a connection to Office 365, but it doesn't return anything. Connect-MsolService cmdlet创建与Office 365的连接,但不返回任何内容。 So re-creating the Runspace if needed is not a work-around because this would be done by the PowerShellInvoker class and the Office365Connector wouldn't know that it has to reconnect to Office 365. (Also this would not solve the problem.) Combining the two classes isn't a solution either because the PowerShellInvoker is also used elsewhere. 因此,如果需要的话,重新创建运行空间不是一种解决方法,因为这将由PowerShellInvoker类完成,并且Office365Connector不会知道它必须重新连接至Office365Connector 。(这也无法解决问题。)这两个类都不是解决方案,因为PowerShellInvoker也用在其他地方。

So can anyone tell me how to prevent the Runspace from closing or why it is closed? 那么谁能告诉我如何防止运行空间关闭或为什么关闭它?

Edit: More code 编辑:更多代码

The full PowerShellInvoker class can be found here . 完整的PowerShellInvoker类可在此处找到。

In the Office365Connector class, there is currently much overhead. Office365Connector类中,当前有很多开销。 Here are some snippets: 以下是一些摘要:

Initialization in constructor: 在构造函数中初始化:

var cred = new PSCredential(adminUpn, adminPassword);

_psi = new PowerShellInvoker("MSOnline");
_psi.ExecuteCommand("Connect-MsolService", new { Credential = cred });

Method to retrieve the license for a UPN: 检索UPN许可证的方法:

public IEnumerable<string> GetUserLicenses(string upn)
{
    PSObject[] licenses = _psi.ExecuteScript(string.Format("(Get-MsolUser -UserPrincipalName \"{0}\").Licenses | % {{ $_.AccountSkuId }}", upn)).ToArray();

    // no licenses: a list with one element (which is null) is returned.
    if (licenses.Length == 1 && licenses[0] == null)
    {
        licenses = new PSObject[0];
    }

    return licenses.Select(pso => pso.ToString()).ToList();
}

As you can see, I added some ToList s to the method return values (especially in the PowerShellInvoker ). 如您所见,我在方法的返回值中添加了一些ToList (特别是在PowerShellInvoker )。 I did this because I wanted to prevent lazy execution of the enumerables because I thought this could be the reason for the closed Runspace. 我这样做是因为我想防止延迟枚举的执行,因为我认为这可能是封闭Runspace的原因。

OK, this was a stupid mistake by myself - although I don't really understand why MVC is doing this: 好的,这是我自己一个愚蠢的错误-尽管我不太了解MVC为何这样做:

In one of my many attempts to solve the problem I moved the disposal code of my Office365Connector to the Dispose method of the application (in Global.asax.cs ). 在解决问题的众多尝试之一中,我将Office365Connector的处置代码移至了应用程序的Dispose方法(位于Global.asax.cs )。 After that I apparently fixed the original error but it didn't work out because of the disposal stuff. 在那之后,我显然修复了原始错误,但是由于处理错误而无法解决。 Apparently the application instance is disposed more often than it is "ended". 显然,应用程序实例比“结束”实例的处置频率更高。 When I moved the code back to Application_End() where it belongs everything worked fine. 当我将代码移回所属的Application_End() ,一切正常。

That makes my original question kind of invalid because I wrote that my dispose code already was in Application_End() . 这使我原来的问题变得无效,因为我写道我的处置代码已经在Application_End() :-\\ : - \\

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM