当Powershell ISE退出时,如何在DLL中清理资源 - 例如New-Pssession和New-Psjob



我正在尝试在C#中实现一组CMDLET,该C#创建和管理外部资源,其模式与New-PsSession,Get-Pssession,删除Pssession相似。

创建的对象需要在自己的线程上进行工作,这是可取消的。

如果我明确处理()每个对象,则可以正常工作。但是,如果我创建一些对象,然后退出powershell iSe而又不会处置,那么powershell_ise.exe进程会徘徊,直到我在任务管理器中杀死它。

我可以通过在powershell iSe中调用寄存器 - 发动机来解决此问题,但这有点黑客,因为DLL未能封装自己的清理。

我尝试了两种方法:

  1. 添加域load事件处理程序
  2. 使用RunSpaceInvoke从DLL内部呼叫寄存器 - 发动机

下面的代码重现了问题:

namespace Test
{
    /// <summary>
    /// An class which encapsulates some asynchronous work in a background thread.
    /// Implements IDisposable to cancel the work and cleanup.
    /// </summary>
    public class Thing : IDisposable
    {
        #region private fields
        private readonly Thread _thread;
        private readonly CancellationTokenSource _cancellationTokenSource;
        private bool _disposed;
        #endregion
        #region static fields (for on-exit cleanup)
        private static List<Thing> _allThings = new List<Thing>();
        private static bool _registeredDisposeAll;
        #endregion
        #region public fields
        /// <summary>
        /// Simple counter to see asynchronous work happening
        /// </summary>
        public int Counter { set; get; }
        #endregion
        #region Constructor
        public Thing()
        {
            if (!_registeredDisposeAll)
            {
                // The first time this is called, register an on-exit handler
                // Neither of these work :-(
                // Attempt 1:
                // Register a DomainUnload handler
                AppDomain.CurrentDomain.DomainUnload += (o,a) => DisposeAll();
                // Attempt 2:
                // use RunspaceInvoke to call Register-EngineEvent
                using (var ri = new RunspaceInvoke())
                {
                    string script =
                        "Register-EngineEvent -sourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action { [Test.Thing]::DisposeAll() }";
                    ri.Invoke(script);
                }
                _registeredDisposeAll = true;
            }
            // add this to a static registry of running Things
            _allThings.Add(this);
            // and start some asynchronous work in a thread
            _cancellationTokenSource = new CancellationTokenSource();
            var cancel = _cancellationTokenSource.Token;
            Counter = 0;
            _thread = new Thread(() =>
            {
                while (!cancel.IsCancellationRequested)
                {
                    Thread.Sleep(1000);
                    ++Counter;
                }
            });
            _thread.Start();
        }
        #endregion
        #region IDisposable
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)
            {
                _cancellationTokenSource.Cancel();
                _thread.Join();
                _cancellationTokenSource.Dispose();
            }
            _disposed = true;
        }
        /// <summary>
        /// Callback to destroy any remaining things        
        /// If this doesn't get called, powershell won't exit properly
        /// </summary>
        public static void DisposeAll()
        {
            foreach (var thing in _allThings)
            {
                thing.Dispose();
            }
            _allThings.Clear();
        }
        #endregion
    }
    /// <summary>
    /// A Cmdlet to create a new Thing, similar to New-PSSession or New-PSJob
    /// </summary>
    [Cmdlet(VerbsCommon.New, "Thing")]
    public class NewThing : Cmdlet
    {
        protected override void ProcessRecord()
        {
            WriteObject(new Thing());
        }
    }
}

要查看此操作,请将其编译为test.dll,运行powershell_ise.exe,然后...

import-module Test.dll
$thing = New-Thing
exit

PowerShell ISE窗口将关闭,但是由于东西仍在运行,因此过程徘徊。这仅发生在ISE中,而不是常规的powershell.exe(可能是在退出时更残酷的?)。

如果在退出之前进行以下操作,则可以正常工作。

Register-EngineEvent -sourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action { [Test.Thing]::DisposeAll() }

如何获得DLL进行注册?

我在Windows 7 x64上,带有.NET 4.5和PowerShell 3.0

您可以将线程设置为背景线程,这应该允许ISE退出,尽管您的线程将被停止 - 不优雅地关闭IIRC。另一个选择是在您的课程中添加最终确定器。请注意,除非您有最终制度,否则您不需要拨打GC.SuppressFinalize(this);的呼叫

相关内容

最新更新