我正在尝试在C#中实现一组CMDLET,该C#创建和管理外部资源,其模式与New-PsSession,Get-Pssession,删除Pssession相似。
创建的对象需要在自己的线程上进行工作,这是可取消的。
如果我明确处理()每个对象,则可以正常工作。但是,如果我创建一些对象,然后退出powershell iSe而又不会处置,那么powershell_ise.exe进程会徘徊,直到我在任务管理器中杀死它。
我可以通过在powershell iSe中调用寄存器 - 发动机来解决此问题,但这有点黑客,因为DLL未能封装自己的清理。
我尝试了两种方法:
- 添加域load事件处理程序
- 使用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);
的呼叫