C#同步控制台输入输出?

时间:2009-05-11 20:11:44

标签: c# console

我正在编写一个服务器应用程序,我希望它是基于控制台的。我需要用户能够输入不同的命令,但同时有可能在用户写入时将某些内容输出到控制台。这会使缓冲区混乱。有没有干净的方法呢?

感谢。

7 个答案:

答案 0 :(得分:4)

我开始研究测试程序,以展示如何将控制台划分为输出区域和输入区域,输出区域随着输出区域扩展而输出更多。它还不完美,但你可以将它发展成你正在寻找的答案:

  static int outCol, outRow, outHeight = 10;

  static void Main(string[] args)
  {
     bool quit = false;
     System.DateTime dt = DateTime.Now;
     do
     {
        if (Console.KeyAvailable)
        {
           if (Console.ReadKey(false).Key == ConsoleKey.Escape)
              quit = true;
        }
        System.Threading.Thread.Sleep(0);
        if (DateTime.Now.Subtract(dt).TotalSeconds > .1)
        {
           dt = DateTime.Now;
           WriteOut(dt.ToString(" ss.ff"), false);
        }            
     } while (!quit);
  }

  static void WriteOut(string msg, bool appendNewLine)
  {
     int inCol, inRow;
     inCol = Console.CursorLeft;
     inRow = Console.CursorTop;

     int outLines = getMsgRowCount(outCol, msg) + (appendNewLine?1:0);
     int outBottom = outRow + outLines;
     if (outBottom > outHeight)
        outBottom = outHeight;
     if (inRow <= outBottom)
     {
        int scrollCount = outBottom - inRow + 1;
        Console.MoveBufferArea(0, inRow, Console.BufferWidth, 1, 0, inRow + scrollCount);
        inRow += scrollCount;
     }
     if (outRow + outLines > outHeight)
     {
        int scrollCount = outRow + outLines - outHeight;
        Console.MoveBufferArea(0, scrollCount, Console.BufferWidth, outHeight - scrollCount, 0, 0);
        outRow -= scrollCount;
        Console.SetCursorPosition(outCol, outRow);
     }
     Console.SetCursorPosition(outCol, outRow);
     if (appendNewLine)
        Console.WriteLine(msg);
     else
        Console.Write(msg);
     outCol = Console.CursorLeft;
     outRow = Console.CursorTop;
     Console.SetCursorPosition(inCol, inRow);
  }

  static int getMsgRowCount(int startCol, string msg)
  {
     string[] lines = msg.Split('\n');
     int result = 0;
     foreach (string line in lines)
     {
        result += (startCol + line.Length) / Console.BufferWidth;
        startCol = 0;
     }
     return result + lines.Length - 1;
  }

答案 1 :(得分:2)

我个人会使用事件处理程序管理一个同时处理输入和输出的控制台,创建一个类ScreenManager或其他什么,在该类中添加一个void RunProgram()方法,创建一个带有处理程序和必需变量的事件用于读取输入键“Console.ReadKey(bool).key”。

static Consolekey newKey;

在你的主程序上,创建你的类的实例“whatev你叫它”,然后创建一个该实例内部方法的线程,Thread coreThread = new Thread(delegate(){myinstance.myProgramMrthod()}); < / p>

在你的主循环中循环,直到线程启动并运行。 while(!Thread.IsAlive);

然后创建主程序循环。

while (true)
{
}

然后为安全,加入你的自定义线程,这样主程序就不会继续,直到关闭/处理自定义线程。

customThread.Join();

你现在有两个线程单独运行。

回到你的班级,在你的事件处理程序方法中创建一个开关。

switch (newkey)
{
case Consolekey.enter
Console.WriteLine("enter pressed");
break;

ect, ect.

default:
Console.write(newkey); // writes character key that dont match above conditions to the screen. 
break;
}

坚持allyour逻辑,因为你想要处理密钥。 How to use multiple modifier keys in C# 可能会有所帮助。

在你的实例的方法RunProgram()或whatev中你选择调用它,在你完成所需的任何代码之后,创建一个无限循环来检查密钥更改。

while (true)
{
newKey = Console.ReadKey(true).Key;
if (newKey != oldKey)
{
KeyChange.Invoke();
}
}

此循环存储按下的任何键,然后检查是否为新键,如果为true则触发事件。

你现在拥有你所寻找的核心,一个循环请求新密钥的字符串,而主循环可以自由显示你想要显示的任何文本。

我可以想到的两个可修复的错误,一个是“默认”内部开关将打印到控制台的大写字母或字符串。另一个是添加到控制台的任何文本都添加到光标点,因此它会添加到用户刚刚输入的文本中。

我会,因为我刚刚创建它,你如何管理文本被添加到控制台。我再次使用一个事件。我可以在整个过程中使用方法和函数,但事件会增加程序的灵活性,我认为。

好的,所以我们希望能够在控制台中添加文本,而不会让我们输入的输入变得烦恼。保持输入在底部;

创建一个具有带有字符串参数的签名的新委托,void委托myDelegate(字符串Arg)。然后用这个委托创建一个事件,称之为newline,newinput,whatev你喜欢。

事件处理程序将采用字符串参数(重新显示控制台更新文本:您希望在用户输入上方插入控制台)它将获取用户输入控制台的文本,存储它,然后打印将paramiter字符串输出到控制台上,然后打印输入underneith的用户。

我个人选择在方法外部的顶部创建一个静态字符串,将其初始化为空,因为它经常被使用,你不想创建一个新的标识符,然后每次调用该方法时初始化变量,然后在方法结束时处理它,再次重新创建一个新的,再次。

调用字符串“input”或其他。

在keychange事件句柄的默认区域中添加输入+ = newkey。

在Consolekey.enter部分控制台中写入输入然后输入= string.empty或string =“”。

在事件处理程序中添加一些逻辑。

public void OnInsert(string Argument)
        {
            Console.CursorTop -= 1;

    // moves the cursor to far left so new input overwrites the old.



    // if arg string is longer, then print arg string then print input  // string.

            if (Argument.Length > input.Length)
            {
                Console.WriteLine(Argument);
                Console.WriteLine(input);
            }
            else
            {

    // if the users input if longer than the argument text then print
    // out the argument text, then print white spaces to overwrite the
    // remaining input characters still displayed on screen.


                for (int i = 0; i < input.Length;i++ )
                {
                    if (i < Argument.Length)
                    {
                        Console.Write(Argument[i]);
                    }
                    else
                    {
                        Console.Write(' ');
                    }
                }
                Console.Write(Environment.NewLine);
                Console.WriteLine(input);
            }
        }

hope this helps some of you, its not perfect, a quick put together test that works enough to be built on.

答案 2 :(得分:1)

如果您需要在用户输入时允许输出到达,我建议将输出发送到新窗口。因此,您可以拥有一个用于启动应用程序的窗口,然后它会生成一个线程以打开一个新的控制台进行输入,然后它继续将任何输出消息发送到原始窗口。如果你试图将所有内容保存在同一个窗口中,我认为你会遇到太多的资源锁定问题。

答案 3 :(得分:1)

如果将服务器视为客户端/服务器应用程序,则此类问题会变得更加简单。让服务器与发送命令和接收输出的客户端管理应用程序建立“n”连接。客户端应用程序可以完全分离输入和输出,一个线程处理输入,一个处理输出。

如果输入线程正在输入一行,输出线程可能会阻塞,而当线路被取消或提交时,输出线程会解锁。

答案 4 :(得分:1)

我得到了使用Console.MoveBufferArea()的示例,但是请注意,该方法不适用于Windows以外的平台,因为该方法在这些平台上为not implemented

在此示例中,您将在代码中使用Read()代替Console.ReadLine(),并使用Log(...)代替Console.WriteLine(...)

class Program
{
    static void Main(string[] args)
    {
        // Reader
        new Thread(() =>
        {
            string line;
            while ((line = Read()) != null)
            {
                //...
            }
            Environment.Exit(0);
        }).Start();


        // Writer
        new Thread(() =>
        {
            while (true)
            {
                Thread.Sleep(1000);
                Log("----------");
            }
        }).Start();

    }

    static int lastWriteCursorTop = 0;
    static void Log(string message)
    {
        int messageLines = message.Length / Console.BufferWidth + 1;
        int inputBufferLines = Console.CursorTop - lastWriteCursorTop + 1;

        Console.MoveBufferArea(sourceLeft: 0, sourceTop: lastWriteCursorTop,
                               targetLeft: 0, targetTop: lastWriteCursorTop + messageLines,
                               sourceWidth: Console.BufferWidth, sourceHeight: inputBufferLines);

        int cursorLeft = Console.CursorLeft;
        Console.CursorLeft = 0;
        Console.CursorTop -= inputBufferLines - 1;
        Console.WriteLine(message);
        lastWriteCursorTop = Console.CursorTop;
        Console.CursorLeft = cursorLeft;
        Console.CursorTop += inputBufferLines - 1;
    }
    static string Read()
    {
        Console.Write(">"); // optional
        string line = Console.ReadLine();
        lastWriteCursorTop = Console.CursorTop;
        return line;
    }
}

答案 5 :(得分:0)

您是否尝试过调用OpenStandardInput,读取任何输入并重置其位置,然后写入输出流。然后,您可以再次调用OpenStandardInput并将数据填回流中。

答案 6 :(得分:0)

我认为没有完美的方法来实现这一目标。 telnet做了什么(至少我使用的最后一个版本)没有打印任何输入(只是读取击键)并只是在输出到达时打印输出。另一种方法是将需要输出的任何数据存储在缓冲区中,并且只有在用户输入命令后才打印出来。 (你甚至可以给输出添加时间戳,使其更加明显。)我真的看不到更好的选择 - 你不可避免地会遇到使用同步I / O接口(即命令行)和后端的异步操作。