当在后台运行Mono应用程序时,我能否检测到缺少控制台?

3

我有一个.NET 4.5控制台应用程序,我正在使用Mono在CentOS上运行。代码结束于:

Console.ReadLine();

如果我交互式地运行应用程序,那么它会按照我的预期运行,Console.ReadLine() 等待键盘输入,但是,如果我使用 nohup 在后台运行应用程序...

nohup mono Program.exe > Program.log &

然后,Program.log 显示 Console.ReadLine() 引起了奇怪的异常:

System.UnauthorizedAccessException: Access to the path "/home/user/[Unknown]" is denied.
  at System.IO.FileStream.ReadData (IntPtr handle, System.Byte[] buf, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at System.IO.FileStream.ReadInternal (System.Byte[] dest, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at System.IO.FileStream.Read (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at System.IO.StreamReader.ReadBuffer () [0x00000] in <filename unknown>:0
  at System.IO.StreamReader.ReadLine () [0x00000] in <filename unknown>:0
  at System.IO.UnexceptionalStreamReader.ReadLine () [0x00000] in <filename unknown>:0
  at System.IO.SynchronizedReader.ReadLine () [0x00000] in <filename unknown>:0
  at System.Console.ReadLine () [0x00000] in <filename unknown>:0
  at Program1.Program.Main (System.String[] args) [0x00000] in <filename unknown>:0

我当然可以捕获并静默忽略异常,但我很好奇是否有可能检测到我没有控制台,从而改变我的应用程序行为?

3个回答

2
经过一些试验,这对我来说似乎能够胜任:
if (Console.In is StreamReader) {
    Console.WriteLine("Interactive"); 
} else {
    Console.WriteLine("Background"); 
}

我不确定它是否适用于所有可能的标准输入重定向等操作,但对于我的目的来说,它足够好用。


1

2
我检查了一下,最初看到在后台运行我的应用程序时,Environment.UserInteractive返回了False值,这让我感到鼓舞。但不幸的是,当交互式运行时,我也得到了一个False值。更深入地看,参见https://github.com/mono/mono/blob/master/mcs/class/corlib/System/Environment.cs和第297行的注释,其中写着“[MonoTODO(“当前始终返回false,无论交互状态如何”]”。 - Elliveny
@Elliveny 我还没有研究过那个。目前看来,你应该手动覆盖该方法,例如使用try catch,并在catch块中返回false :) - nsgocev

0

基本上我通常想知道的是:

  • 它是否作为服务运行
  • 它是否作为控制台运行

我猜你的问题也是关于这个的...我会写下步骤,因为我花了太长时间才弄清楚所有细节...

设置你的项目

最简单的方法是创建一个windows service C#应用程序,并在Program.cs中开始编码。

基本上我在这里做的是:

  • 如果是Mono,则检查TTY是否连接-> Console,mono
  • 如果是Windows,则检查UserInteractive
  • 如果是交互式的,则分配一个控制台,以使事情正常进行。
  • 如果这不起作用,我们只能假设是控制台应用程序。

以下是代码:

public static ProgramType ProgramType
{
    get
    {
        if (!programType.HasValue)
        {
            try
            {
                if (Type.GetType("Mono.Runtime") != null)
                {
                    // It's a console application if 'bool Mono.Unix.Native.Syscall.isatty(0)' in Mono.Posix.dll
                    var monoPosix = System.Reflection.Assembly.Load("Mono.Posix, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756");
                    Type syscallType = monoPosix.GetType("Mono.Unix.Native.Syscall");
                    var method = syscallType.GetMethod("isatty");
                    bool isatty = (bool)method.Invoke(null, new object[] { 0 });

                    if (isatty)
                    {
                        programType = ProgramType.MonoConsole;
                    }
                    else
                    {
                        programType = ProgramType.MonoService;
                    }
                }
                else
                {
                    if (Environment.UserInteractive)
                    {
                        programType = ProgramType.WindowsConsole;
                    }
                    else
                    {
                        programType = ProgramType.WindowsService;
                    }
                }
            }
            catch
            {
                programType = ProgramType.Unknown;
            }
        }
        return programType.Value;
    }
}

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AllocConsole();

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FreeConsole();

[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(int dwProcessId);

[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);


/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
    switch (ProgramType)
    {
        case ProgramType.WindowsConsole:
            {
                // WindowsConsole application
                //
                // Get a pointer to the forground window. The idea here is that
                // IF the user is starting our application from an existing console
                // shell, that shell will be the uppermost window. We'll get it
                // and attach to it

                IntPtr ptr = GetForegroundWindow();

                int u;
                GetWindowThreadProcessId(ptr, out u);

                try
                {
                    Process process = Process.GetProcessById(u);

                    if (process.ProcessName == "cmd")
                    {
                        // Is the uppermost window a cmd process?
                        AttachConsole(process.Id);
                    }
                    else
                    {
                        // No console AND we're in console mode ... create a new console.
                        AllocConsole();
                    }

                    // Console is now accessible.
                    NubiloSoft.Util.Logging.Sink.Console.Register();

                    // Arguments?

                    StartConsoleService();
                }
                finally
                {
                    FreeConsole();
                }
            }
            break;

        case ProgramType.MonoConsole:
            {
                // Console is now accessible.
                NubiloSoft.Util.Logging.Sink.Console.Register();

                // Arguments?
                StartConsoleService();
            }
            break;

        case ProgramType.MonoService:
        case ProgramType.WindowsService:
            {
                // Start service
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[]
                {
                    new Service1()
                };
                ServiceBase.Run(ServicesToRun);
            }
            break;

        default:
            Console.WriteLine("Unknown CLR detected. Running as console.");
            {
                // Console is now accessible.
                NubiloSoft.Util.Logging.Sink.Console.Register();

                // Arguments?
                StartConsoleService();
            }
            break;
    }
}

我通常使用默认的Service1来调用某些静态Startup类上的'Start'和'Stop'。它与StartConsoleService的工作方式相同。由于ProgramType是公开的,您可以在需要时使用它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接