简体   繁体   English

C#多线程控制台动画

[英]C# Multi-Threaded Console Animation

I've been working on a console game, In which I animate. 我一直在做一个控制台游戏,在其中制作动画。 Normally this could be done easily in the console, but I have each in a separate thread so the game can continue during the animation. 通常,这可以在控制台中轻松完成,但是我每个人都有一个单独的线程,因此游戏可以在动画过程中继续进行。

static void Main(string[] args)
    {
        animateLine(wave, 50, 10, Console.CursorLeft, Console.CursorTop);
        Console.Read();
    }

    static void animateLine(string[] an, int i, int sec, int x, int y)
    {
        new Thread(delegate ()
        {
            Console.Write("  ");
            Console.Write("\b");
            sec = sec * 1000;
            var time = Stopwatch.StartNew();
            while (time.ElapsedMilliseconds < sec)
            {
                Console.SetCursorPosition(x, y);
                foreach (string val in an)
                {
                    Console.SetCursorPosition(x, y);
                    Console.Write(val);
                    Thread.Sleep(i);
                }
            }
            Console.SetCursorPosition(x, y);
            foreach (char cal in an.GetValue(0).ToString())
            {
                Console.Write(" ");
            }
            foreach (char cal in an.GetValue(0).ToString())
            {
                Console.Write("\b");
            }
            Console.Write(" \b");
            time.Stop();
            time.Reset();
        }).Start();
    }

The above creates an instance of animateLine that takes wave and loops it for 10 seconds. 上面创建了一个animateLine实例,该实例将wave循环10秒钟。 This works all fine and dandy, even when creating two instances, or three total threads. 即使创建两个实例或总共三个线程,这也可以正常工作。

static void Main(string[] args)
    {
        animateLine(wave, 50, 10, Console.CursorLeft, Console.CursorTop);
        Console.Write(" \n");
        animateLine(wave, 50, 10, Console.CursorLeft, Console.CursorTop);
        Console.Read();
    }

However, when the count of additional threads exceeds 2 it begins to break. 但是,当其他线程数超过2时,它开始中断。 On certain occasions, extra pieces of one frame of an animation is left behind where the wave would normally just include 11 characters, an additional wave stuck in one frame will appear beside it, and remain after the animation completes. 在某些情况下,动画的一帧多余部分会留在后面,该wave通常仅包含11个字符,卡在一帧中的附加wave会出现在它的旁边,并在动画完成后保留。 I've tried other ways of running the threads but they simply break the animation in other ways, this threading is the only one I've found capable of producing a smooth animation. 我尝试了其他方式运行线程,但是它们只是以其他方式破坏了动画,这是我发现的唯一能够产生平滑动画的线程。

Commenter Ron Beyer's observation are correct. 评论员罗恩·拜尔的观察是正确的。 Your threads are racing for control of the console output, and each can be pre-empted by the other thread before it is done writing a single coherent unit of output. 您的线程正在争夺控制台输出的控制权,并且在写入单个一致的输出单元之前,每个线程都可以被另一个线程抢占。

The fix for this is standard multi-threading 101: put a lock around the sections of code that need to operate as atomic units without interruption. 解决此问题的方法是标准的多线程101:在需要作为原子单元运行而不会中断的代码段周围放置一个锁。 Here is a version of your code that shows what I mean: 这是您的代码版本,显示我的意思:

class Program
{
    static void Main(string[] args)
    {
        string[] wave = { "-", "/", "|", "\\" };
        animateLine(wave, 50, 10, Console.CursorLeft, Console.CursorTop);
        lock (_lock)
        {
            Console.Write(" \n");
        }
        animateLine(wave, 50, 10, Console.CursorLeft, Console.CursorTop);
        Console.Read();
    }

    private static readonly object _lock = new object();

    static void animateLine(string[] an, int i, int sec, int x, int y)
    {
        Thread thread = new Thread(delegate()
        {
            Console.Write("  ");
            Console.Write("\b");
            sec = sec * 1000;
            var time = Stopwatch.StartNew();
            while (time.ElapsedMilliseconds < sec)
            {
                foreach (string val in an)
                {
                    lock (_lock)
                    {
                        Console.SetCursorPosition(x, y);
                        Console.Write(val);
                    }
                    Thread.Sleep(i);
                }
            }

            lock(_lock)
            {
                Console.SetCursorPosition(x, y);
                foreach (char cal in an.GetValue(0).ToString())
                {
                    Console.Write(" ");
                }
                foreach (char cal in an.GetValue(0).ToString())
                {
                    Console.Write("\b");
                }
                Console.Write(" \b");
            }
            time.Stop();
            time.Reset();
        });

        thread.IsBackground = true;
        thread.Start();
    }
}

The two most obvious sections are in the animateLine() method, where SetCursorPosition() is called before writing some text to the screen. 最明显的两个部分在animateLine()方法中,在将一些文本写入屏幕之前,将调用SetCursorPosition() It's critical that nothing disturb the cursor position as this text is written, otherwise you get corrupted output. 至关重要的是,在编写此文本时,任何内容都不能打扰光标位置,否则会损坏输出。 Putting a lock around those sections of code ensures that all of the output for a given starting cursor position is completed before any other output is sent. 在代码的这些部分周围lock可确保在发送任何其他输出之前,已完成给定起始光标位置的所有输出。

Perhaps slightly less obvious is the Console.WriteLine() in the Main() method. 也许稍微不太明显的是Main()方法中的Console.WriteLine() But this is for the same reason: while this part of the code isn't setting the cursor position, it absolutely has the potential for affecting cursor position and so you also want to limit its operation to outside the times when the other critical sections are executing. 但这是出于相同的原因:虽然这部分代码未设置光标位置,但它绝对有可能影响光标位置,因此您还希望将其操作限制在其他关键部分被占用的时间之外执行。

Note that you need these locks in all three places. 请注意,在所有三个位置都需要这些锁。 The lock statement is used to ensure mutually exclusive execution of the protected sections of code. lock语句用于确保互斥执行代码的受保护部分。 Putting a lock around just one of the sections would not prevent any of the other sections from executing while that one section was executing. 仅在其中一个节上放置锁不会阻止其他任何节在该节执行时执行。 The runtime would have no way to know what sections of code need to be mutually exclusive; 运行时将无法知道哪些代码段需要互斥; it's up to the programmer to indicate that completely with lock statements in the appropriate places. 程序员可以在适当的地方完全使用lock语句来表明这一点。

Note also that the mutual exclusion is based on the object provided to the lock statement. 还要注意,互斥是基于提供给lock语句的对象的。 For any given locked section of code, it will only be mutually exclusive with any other critical section locked with the same object . 对于任何给定的代码锁定段,该代码段仅与与同一对象锁定的任何其他关键段互斥。 So in more complicated scenarios, you can provide finer-grained locking — ie avoid having unrelated critical sections being mutually exclusive between each other — by associating different locking objects with different groups of code that need mutually exclusive execution. 因此,在更复杂的场景中,您可以通过将不同的锁定对象与需要互斥执行的不同代码组相关联,来提供更细粒度的锁定(即避免不相关的关键部分在彼此之间互斥)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM