简体   繁体   中英

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. 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. 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. 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. 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. 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.

Perhaps slightly less obvious is the Console.WriteLine() in the Main() method. 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. 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.

Note also that the mutual exclusion is based on the object provided to the lock statement. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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