【问题标题】:Pausing current thread via user input, in console window application在控制台窗口应用程序中通过用户输入暂停当前线程
【发布时间】:2012-01-22 18:09:35
【问题描述】:

这是我想出的在控制台中暂停/恢复活动线程的解决方案......

它需要第二个线程用于输入,主循环也会做出反应。但是我对 C# 的无知让我想知道是否有更简单的解决方案?也许可以单独使用主线程来完成?

基本上,我正在测试几件将以定义的 FPS 进行迭代的东西,并且我希望能够通过键盘输入暂停和恢复迭代。任何反馈都表示赞赏。

class TestLoops
{
    int targetMainFPS = 5;
    int targetInputFPS = 3;
    bool CONTINUE = true;

    Thread testLoop, testLoop2;
    ConsoleKeyInfo cki;

    ManualResetEvent resetThread = new ManualResetEvent(true); //How to correctly pause threads in C#.
        public void Resume() { resetThread.Set(); }
        public void Pause() { resetThread.Reset(); }

    public TestLoops()
    {
        //_start = DateTime.Now.Ticks;
        Console.Write("CreatingLoop...");

        this.testLoop = new Thread(MainLoop);
        this.testLoop.Start();

        this.testLoop2 = new Thread(InputLoop);
        this.testLoop2.Start();
    }

    void MainLoop()
    {
        long _current = 0;
        long _last = 0;

        Console.Write("MainLoopStarted ");
        while(CONTINUE)
        {
            resetThread.WaitOne();

            _current = DateTime.Now.Ticks / 1000;
            if(_current > _last + (1000 / targetMainFPS) )
            {
                _last = _current;

                //Do something...
                Console.Write(".");

            }
            else
            {
                System.Threading.Thread.Sleep(10);
            }
        }
    }

    void InputLoop()
    {
        long _current = 0;
        long _last = 0;

        Console.Write("InputLoopStarted ");
        while(CONTINUE)
        {
            _current = DateTime.Now.Ticks / 1000;
            if(_current > _last + (1000 / targetInputFPS))
            {
                _last = _current;

                //Manage keyboard Input
                this.cki = Console.ReadKey(true);
                //Console.Write(":");
                if(this.cki.Key == ConsoleKey.Q)
                {
                    //MessageBox.Show("'Q' was pressed.");
                    CONTINUE = false;
                }

                if(this.cki.Key == ConsoleKey.P)
                {
                    this.Pause();
                }
                if(this.cki.Key == ConsoleKey.R)
                {
                    this.Resume();
                }
            }
            else
            {
                System.Threading.Thread.Sleep(10);
            }
        }
    }

    public static void Main(string[] args)
    {
        TestLoops test = new TestLoops();
    }

}

【问题讨论】:

  • 帧速率通常不会通过暂停任意值(如您选择的 10 毫秒)来维持。无论暂停/恢复功能如何,您实际上想要完成什么?
  • 没有什么比重复一些事情直到我对我所看到的感到满意(在这种情况下重复骰子机制的结果)。它主要用于测试。但是,我正在努力学习,如果有更合适的方法,我想学习。
  • 好奇...为什么要从我的标题中删除 C#? ://

标签: c# multithreading input console


【解决方案1】:

您可以通过使用非阻塞Console.KeyAvailable 检查大大简化它。这样,您就可以从主线程执行所有代码:

using System;
using System.Threading;

class TestLoops
{
    const int targetFPS = 5;

    public static void Main(string[] args)
    {
        bool _continue = true;
        DateTime _last = DateTime.MinValue;

        while (_continue)
        {
            if (Console.KeyAvailable)
            {
                ConsoleKeyInfo cki = Console.ReadKey(true);

                if (cki.Key == ConsoleKey.Q)
                {
                    Console.WriteLine("\n'Q' was pressed.");
                    _continue = false;
                }

                if (cki.Key == ConsoleKey.P)
                {
                    Console.WriteLine("\n'P' was pressed; press 'R' to resume.");

                    // Block until 'R' is pressed.
                    while (Console.ReadKey(true).Key != ConsoleKey.R)
                        ; // Do nothing.

                    Console.WriteLine("'R' was pressed.");
                }
            }

            DateTime _current = DateTime.Now;
            if (_current - _last > TimeSpan.FromMilliseconds(1000F / targetFPS))
            {
                _last = _current;

                // Do something...
                Console.Write(".");
            }
            else
            {
                System.Threading.Thread.Sleep(10);
            }
        }        
    }
}

编辑:回复评论。

小心。下面的代码由于Console.ReadKey 调用而阻塞,而不是由于while 循环。 while 循环的存在只是为了,如果用户输入了 R 以外的字符,程序将丢弃它并等待输入另一个字符。

// Block until 'R' is pressed.
while (Console.ReadKey(true).Key != ConsoleKey.R)
    ; // Do nothing.

如果你想阻止直到任何字符被按下,你只需使用:

Console.ReadKey(true);

【讨论】:

  • 第二行应该是:'R' was pressed; press 'P' to pause again?
  • 谢谢,这样就可以了。不过我很好奇。被阻塞时,线程是否基本上暂停等待另一个 ConsoleKey 输入?这意味着 ManualResetEvent 只不过是该类型块的包装版本?
  • 刚刚注意到 DateTime 的简化...谢谢!这也很有帮助。
  • 在某种程度上,是的。有几种阻塞机制:Thread.Sleep 等待时间过去,Console.ReadKey 等待键盘输入,ManualResetEvent.WaitOne 等待信号(通常由用户代码本身发出)。阻塞Console.ReadKey 调用在功能上等同于等待ManualResetEvent,当键盘输入可用时(由另一个线程)发出信号。
  • 我从你的例子中学到了一些东西。即,简化 DateTime,通过内部无操作循环阻塞线程,并且 Main() 已经是一个线程(因此不需要显式创建线程)......非常有帮助。
【解决方案2】:

这行得通,是的。您唯一可以考虑的是确保当您按下 Q 键时工作线程停止,即使它之前被挂起:

if(this.cki.Key == ConsoleKey.Q)
{
     CONTINUE = false;
     this.Resume(); // <-- make sure that the worker thread resumes
}

附带说明一下,恒定的 FPS 通常通过跳帧来维持,而不是暂停线程。暂停线程是不精确的,并且不允许微调动画速度。 Thread.Sleep 在不同硬件上的行为也可能不同,并且分辨率可能相当差(最小 OS 时间片约为 ~16ms IIRC)。

【讨论】:

  • 我对跳帧不熟悉。 :/ 如果我能在屏幕上放一些东西,我需要学习一些新东西。至于现在,我使用 Unity 进行渲染,并使用控制台在没有实际游戏空间的情况下测试数学和对象想法。
猜你喜欢
  • 2014-11-25
  • 1970-01-01
  • 2021-03-11
  • 2023-04-10
  • 1970-01-01
  • 2019-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多