2

我们的 C# 应用程序以代码 0 退出,即使它在代码中显式返回 -1:

internal class Program
{
    public int Main()
    {
        ....
        return -1;
    }
}

如果使用了同样的情况void Main

internal class Program
{
    public void Main()
    {
        ....
        Environment.Exit(-1);
    }
}

正如关于 SO 的其他问题所暗示的那样,它可能是其他线程中未处理的 CLR/C++/native 异常。但是,我在最后一个线程之前添加了所有托管/本机线程的正常关闭,但行为仍然存在。

可能是什么原因?

4

1 回答 1

2

事实证明这是因为我们使用JobObjects来确保所有子进程在当前进程退出时使用 C 中的此代码退出(我们实际上是从 C# 调用的):

HANDLE h = ::CreateJobObject(NULL, NULL);

JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
::ZeroMemory(&info, sizeof(info));
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
::SetInformationJobObject(h, JobObjectExtendedLimitInformation, &info, sizeof(info));
::AssignProcessToJobObject(h, ::GetCurrentProcess());

...

::CloseHandle(h);

return -1;

此代码将当前进程及其所有子进程添加到将在当前进程退出时关闭的作业对象。但是它在被调用时会产生副作用,CloseHandle它会杀死当前进程而不会到达 line return -1。而且由于JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEflag 会自动杀死所有进程,因此无法为所有进程设置退出代码,因此 OS 以退出代码 0 退出进程。

在 C# 中,我们遵循标准准则来清理资源并使用 -SafeHandle派生类来确保CloseHandle调用它并且发生完全相同的情况 - 在 CLR 实际退出之前,它::CloseHandle为所有SafeHandles 调用,忽略返回值和Environment.Exit.

然而更有趣的是,如果CloseHandle在 C# 和 C++ 中都删除了显式(或不那么显式)调用,操作系统仍将在 CLR/CRT 退出后关闭进程退出时的所有句柄,并且实际的退出代码将被退回。所以有时最好不要清理资源:-) 或者换句话说,在::ExitProcess调用本机之前,你不能保证退出代码是完整的。

因此,为了解决这个特定问题,我可以AssignProcessToJobObject在子进程启动时调用,也可以删除对CloseHandle. 我选择了第一种方法。

于 2013-10-09T23:46:05.767 回答