[英]C# Multi-Threaded Console 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();
}
上面创建了一个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时,它开始中断。 在某些情况下,动画的一帧多余部分会留在后面,该wave通常仅包含11个字符,卡在一帧中的附加wave会出现在它的旁边,并在动画完成后保留。 我尝试了其他方式运行线程,但是它们只是以其他方式破坏了动画,这是我发现的唯一能够产生平滑动画的线程。
评论员罗恩·拜尔的观察是正确的。 您的线程正在争夺控制台输出的控制权,并且在写入单个一致的输出单元之前,每个线程都可以被另一个线程抢占。
解决此问题的方法是标准的多线程101:在需要作为原子单元运行而不会中断的代码段周围放置一个锁。 这是您的代码版本,显示我的意思:
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
可确保在发送任何其他输出之前,已完成给定起始光标位置的所有输出。
也许稍微不太明显的是Main()
方法中的Console.WriteLine()
。 但这是出于相同的原因:虽然这部分代码未设置光标位置,但它绝对有可能影响光标位置,因此您还希望将其操作限制在其他关键部分被占用的时间之外执行。
请注意,在所有三个位置都需要这些锁。 lock
语句用于确保互斥执行代码的受保护部分。 仅在其中一个节上放置锁不会阻止其他任何节在该节执行时执行。 运行时将无法知道哪些代码段需要互斥; 程序员可以在适当的地方完全使用lock
语句来表明这一点。
还要注意,互斥是基于提供给lock
语句的对象的。 对于任何给定的代码锁定段,该代码段仅与与同一对象锁定的任何其他关键段互斥。 因此,在更复杂的场景中,您可以通过将不同的锁定对象与需要互斥执行的不同代码组相关联,来提供更细粒度的锁定(即避免不相关的关键部分在彼此之间互斥)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.