我正在尝试在C#中实现一组cmdlet,用于创建和管理外部资源,其模式与New-PSSession,Get-PSSession,Remove-PSSession类似。
创建的对象需要在自己的线程上工作,这是可以取消的。
如果我明确Dispose()每个对象,它工作正常。但是如果我创建了一些对象,然后在没有Disposing的情况下退出Powershell ISE,powershell_ise.exe进程会一直挂起,直到我在任务管理器中将其终止。
我可以通过在Powershell ISE中调用Register-EngineEvent来解决这个问题,但这有点像黑客,因为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窗口将关闭,但由于Thing仍在运行,因此进程仍然存在。这只发生在ISE上,而不是常规的powershell.exe(当它退出时可能更残酷?)。
如果在退出之前执行以下操作,那么它可以正常工作。
Register-EngineEvent -sourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action { [Test.Thing]::DisposeAll() }
如何让DLL注册自己的清理?
我使用的是带有.NET 4.5和Powershell 3.0的Windows 7 x64
答案 0 :(得分:1)
您可以将线程设置为后台线程,并且应该允许ISE退出,尽管您的线程将被停止 - 而不是正常关闭IIRC。另一种选择是为你的班级添加终结者。请注意,除非您有终结器,否则您不需要拨打GC.SuppressFinalize(this);
,所以也许你确实有一个,但它没有在上面列出?