如何在线程应用程序中的console.writeline期间阻止console.readline中断



最近我开始使用VB.Net开发一个基于多线程控制台的应用程序,遇到了一个(可解释的)错误。程序本身是一个服务器,在不同的线程中接收来自外部应用程序的套接字连接。我还有一个单独的线程,它只需等待用户使用console.readline在应用程序中输入文本(这样就可以输入服务器命令,如HELP或STATUS)。

问题是程序从不同的套接字线程输出消息,如果我正在键入命令,它就会被中断。如果我输入命令"status",它会正常工作,但对于输入命令的用户来说,它会被破坏,他们可能无法看到自己是否键入了错误的命令。例如:

sta已连接新客户端
tus

我知道为什么会发生这种情况,但我很好奇是否有一个简单的解决方案可以阻止这种情况(在输入过程中不必暂停其他线程),或者是否有一种更简单的方法可以允许输入控制台命令,而这些命令不会在其他应用程序输出中移动。

几个建议:

  • 编写信息在队列中,而不是直接写入控制台。当用户输入命令,从队列中取出所有最近的消息并转储他们到控制台
  • 将消息输出到其他控制台的区域,就像旧的BBS风格的聊天UI。有一部分用于消息的页面和用于输入和用户结果的另一个页面命令。这需要在控制台方面做更多的工作
  • 将消息输出到一个单独的日志文件,用户可以使用tail或类似的东西(或应用程序和用户可以同时打开两个应用程序,其中一个显示消息和其他交互运行命令并查看输出

C夏普等效

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;
namespace testconsole
{
    class Program
    {
        private static bool Running = true;
        private static string Command = "";
        static void Main( string[] args )
        {
            while ( Running )
            {
                ReadInput();
            }//running
        }//main-----------------------------------------
        static void ReadInput()
        {
            ConsoleKeyInfo cki;
            cki = Console.ReadKey();
            if ( cki.Key == ConsoleKey.Escape )
            {
                Command = "";
            }
            else if (cki.Key == ConsoleKey.Backspace)
            {
                ClearConsole();
                if ( Command.Length > 0 )
                {
                    Command = Command.Substring( 0, Command.Length - 1 );
                }
                Console.CursorLeft = 0;
                Console.Write( Command );
            }
            else if ( cki.Key == ConsoleKey.Enter )
            {
                Console.CursorLeft = 0;
                ClearConsole();
                string TempCommand = Command;
                TextOut( Command );
                Command = "";
                //process tempcommand
                if ( TempCommand  == "quit")
                {
                    Running = false;
                }
                if ( TempCommand == "test" )
                {
                    TextOut("this is a test");
                }
            }
            else
            {
                Command += cki.KeyChar;
                Console.CursorLeft = 0;
                Console.Write( Command );
            }
        }//ReadInput-----------------------------------------
        static void ClearConsole()
        {
            for ( int i = 0; i < Command.Length; i++ )
            {
                Console.Write( " " );
            }
        }//ClearConsole-----------------------------------------
        static void TextOut( string Message )
        {
            if (Command != "")
            {
                ClearConsole();
            }
            Console.CursorLeft = 0;
            Console.WriteLine( Message );
            if ( Message != Command )
            {
                Console.Write( Command );
            }
        }//TextOut-----------------------------------------
    }//program
}//namespace

这里有一个略有不同的版本,它支持读行上的前缀,并打包到自己的类中以供重用。它还使用StringBuilder而不是读取行的字符串。

public static class Console2
{
    private static bool _isReading;
    private static string _currentPrefix;
    private static readonly StringBuilder CurrentReadLine = new StringBuilder();
    public static string ReadLine(string prefix = null)
    {
        _currentPrefix = prefix;
        _isReading = true;
        Console.Write(prefix);
        while (true)
        {
            ConsoleKeyInfo cki;
            cki = Console.ReadKey();
            if (cki.Key == ConsoleKey.Escape)
            {
                CurrentReadLine.Clear();
            }
            else if (cki.Key == ConsoleKey.Backspace)
            {
                if (CurrentReadLine.Length > 0)
                {
                    CurrentReadLine.Length--;
                }
                ClearConsole();
                Console.Write(prefix + CurrentReadLine);
            }
            else if (cki.Key == ConsoleKey.Enter)
            {
                Console.WriteLine();
                var result = CurrentReadLine.ToString();
                CurrentReadLine.Clear();
                _isReading = false;
                return result;
            }
            else
            {
                CurrentReadLine.Append(cki.KeyChar);
            }
        }
    }
    static void ClearConsole()
    {
        var length = Console.CursorLeft;
        Console.CursorLeft = 0;
        for (int i = 0; i <= length; i++)
        {
            Console.Write(" ");
        }
        Console.CursorLeft = 0;
    }
    public static void WriteLine(string format, params object[] args)
    {
        ClearConsole();
        Console.WriteLine(format, args);
        if (_isReading)
        {
            Console.Write(_currentPrefix + CurrentReadLine);
        }
    }
}

在接受用户输入时,使用集中式记录器并禁用输出。

我知道这个问题主要是美学问题,但像这样的事情让我很恼火。我不想回答自己的问题,但实际上我根据其他人的一些意见想出了一个相当不错的解决方案。我会附上一个代码示例,以防将来有人想做类似的事情。我知道可能有更好的方法可以做到这一点,但以下是我所做的。

基本上,我没有从每个线程中使用console.writeline,而是创建了一个新的子程序(TextOut),它背后有一些逻辑。我还更新了输入线程,将每个字符读取到共享字符串中,而不是使用console.readline()

Private command As String = ""
Private Sub ConsoleInput()
    Dim cki As ConsoleKeyInfo
    cki = Console.ReadKey()
    If cki.Key = ConsoleKey.Escape Then
        command = "" 'Clear command
    ElseIf cki.Key = ConsoleKey.Backspace Then
        If Len(command) > 0 Then 'Make sure you don't go out of bounds
            For i As Integer = 0 To Len(command)
                Console.Write(" ") 'Clear output since new string will be shorter
            Next
            command = Left(command, Len(command) - 1) 'Shorten command by 1 char
        End If
        Console.CursorLeft = 0 'Set the cursor to the beginning of the line
        Console.Write(command) 'Write the command to the screen
    ElseIf cki.Key = ConsoleKey.Enter Then 'Command has been submitted, start checking
        Console.CursorLeft = 0 'Set the cursor to the beginning of the line
        For i As Integer = 0 To Len(command)
            Console.Write(" ") 'Clear output from command (hides the executed command)
        Next
        Dim tempCMD As String = command
        command = ""
        'If/then statements for validating command goes here
        command = "" 'Clear command to allow new input
    Else
        command += cki.KeyChar 'Add char to command string
        Console.CursorLeft = 0 'Set the cursor to the beginning of the line
        Console.Write(command) 'Write the command to the screen
    End If
    ConsoleInput() 'Loop for more input 
End Sub

Sub TextOut(ByVal message As String)
    If command <> "" Then
        For i As Integer = 0 To Len(command)
            Console.Write(" ") 'Clears output in case output is shorter than current command
        Next
        Console.CursorLeft = 0 'Sets cursor to beginning of row
    End If
    Console.WriteLine(message) 'Writes the current message to the screen
    If message <> command Then
        Console.Write(command) 'Writes the command back to the screen
    End If
End Sub

最新更新