C#多线程控制台动画

时间:2015-11-04 04:13:41

标签: c# multithreading animation console

我一直在制作控制台游戏,我在其中制作动画。通常这可以在控制台中轻松完成,但我将每个都放在一个单独的线程中,这样游戏就可以在动画中继续。

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();
    }

上面创建了一个animateLine实例,它接受wave并将其循环10秒。即使在创建两个实例或三个总线程时,这也可以正常工作。

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();
    }

然而,当额外线程的数量超过2时,它开始中断。在某些情况下,动画的一帧的额外片段留在波浪通常仅包括11个字符的位置,在一帧中粘贴的附加波将出现在其旁边,并在动画完成后保留。我尝试了其他运行线程的方法,但它们只是以其他方式破坏了动画,这个线程是我发现能够产生平滑动画的唯一方法。

1 个答案:

答案 0 :(得分:1)

评论员Ron Beyer的观察是正确的。你的线程正在争夺控制台输出的控制权,并且在编写单个连贯的输出单元之前,每个线程都可以被其他线程抢占。

对此的修复是标准的多线程101:锁定需要作为原子单元运行的代码部分而不会中断。这是您的代码的一个版本,显示我的意思:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:better="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="NewApi"
    android:viewportWidth="24"
    better:viewportWidth="24"
    android:viewportHeight="24"
    better:viewportHeight="24"
    android:width="24dp"
    android:height="24dp">
    <path
        android:pathData="M10.09 15.59l1.41 1.41 5 -5 -5 -5 -1.41 1.41 2.58 2.59 -9.67 0 0 2 9.67 0 -2.58 2.59zM19 3L5 3C3.89 3 3 3.9 3 5l0 4 2 0 0 -4 14 0 0 14 -14 0 0 -4 -2 0 0 4c0 1.1 0.89 2 2 2l14 0c1.1 0 2 -0.9 2 -2L21 5C21 3.9 20.1 3 19 3Z"
        better:pathData="M10.09 15.59l1.41 1.41 5 -5 -5 -5 -1.41 1.41 2.58 2.59 -9.67 0 0 2 9.67 0 -2.58 2.59zM19 3L5 3C3.89 3 3 3.9 3 5l0 4 2 0 0 -4 14 0 0 14 -14 0 0 -4 -2 0 0 4c0 1.1 0.89 2 2 2l14 0c1.1 0 2 -0.9 2 -2L21 5C21 3.9 20.1 3 19 3Z"
        android:fillColor="@color/menu_color_selector"
        better:fillColor="@color/menu_color_selector" />
</vector>

两个最明显的部分是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(); } } 方法,在将一些文本写入屏幕之前调用animateLine()。在写入此文本时,任何事都不会干扰光标位置至关重要,否则会导致输出损坏。在代码的这些部分周围放置SetCursorPosition()可确保在发送任何其他输出之前完成给定起始光标位置的所有输出。

lock方法中的Console.WriteLine()可能稍微不那么明显。但这也是出于同样的原因:虽然这部分代码不是设置光标位置,但它绝对有可能影响光标位置,所以你也想要将其操作限制在其他关键部分执行的时间之外。

请注意,您需要在所有三个位置都使用这些锁。 Main()语句用于确保受保护的代码段的互斥执行。只锁定其中一个部分并不会阻止任何其他部分在执行该部分时执行。运行时无法知道哪些代码段需要互斥;程序员可以在适当的地方用lock语句表示完全。

另请注意,互斥基于提供给lock语句的对象。对于任何给定的锁定代码段,它只与在中使用相同对象锁定的任何其他关键部分互斥。因此,在更复杂的场景中,您可以通过将不同的锁定对象与需要互斥执行的不同代码组相关联来提供更精细的锁定 - 即避免让不相关的关键部分彼此互斥 -