c#中使用多线程的问题
本文关键字:问题 多线程 | 更新日期: 2023-09-27 18:08:53
我想实现一个程序,显示一些字符在控制台上随机移动,每个字符都有不同的速度。
我创建了一个递归方法,在控制台上随机移动一个字母。当我想移动两个字母时,我使用两个线程调用相同的方法。
程序工作完美的第一分钟,但一段时间后,字母开始出现在控制台到处!
我真的很确定我的递归方法是好的(我甚至尝试创建另一个方法,这次只是使用while(I <100000)而不是递归。但同样的错误出现了)。有人能帮我一下吗?
非常感谢。
编辑:对不起,这里有一个示例代码(不要考虑如果字母放在相同的位置会发生什么)。字母在"体育场"上移动,它们在x轴上移动20 - 51,在y轴上移动5 - 26。
public void WriteAt(string s, int x, int y)
{
try
{
Console.SetCursorPosition(x, y);
Console.Write(s);
}
catch (ArgumentOutOfRangeException e)
{
Console.Clear();
Console.WriteLine(e.Message);
}
}
public void impresion()
{
int x = random.Next(20, 51);
int y = random.Next(5, 26);
WriteAt("A", x, y);
imprimir("A", x, y, 80);
}
public void impresion2()
{
int x = random.Next(20, 51);
int y = random.Next(5, 26);
WriteAt("E", x, y);
imprimir2("E", x, y, 20);
}
public void go()
{
Thread th1 = new Thread(impresion);
Thread th2 = new Thread(impresion2);
th1.Start(); //creates an 'A' that will move randomly on console
th2.Start(); //creates an 'E' that will move randomly on console
}
public void imprimir(string s, int x, int y, int sleep)
{
Thread.Sleep(sleep);
WriteAt(" ", x, y);
int n = random.Next(1, 5);
if (n == 1)
{
if ((x + 1) > 50)
{
WriteAt(s, x, y);
imprimir(s, x, y, sleep);
}
else
{
WriteAt(s, x + 1, y);
imprimir(s, x + 1, y, sleep);
}
}
else if (n == 2)
{
if ((y - 1) < 5)
{
WriteAt(s, x, y);
imprimir(s, x, y, sleep);
}
else
{
WriteAt(s, x, y - 1);
imprimir(s, x, y - 1, sleep);
}
}
else if (n == 3)
{
if ((x - 1) < 20)
{
WriteAt(s, x, y);
imprimir(s, x, y, sleep);
}
else
{
WriteAt(s, x - 1, y);
imprimir(s, x - 1, y, sleep);
}
}
else
{
if ((y + 1) > 25)
{
WriteAt(s, x, y);
imprimir(s, x, y, sleep);
}
else
{
WriteAt(s, x, y + 1);
imprimir(s, x, y + 1, sleep);
}
}
}
线程可能有无数微妙的问题——任何访问共享资源的都必须被认为是可疑的。
考虑一个move-position- after -a-put-character 不是原子的,一个线程可能会中断另一个线程,从而导致一个move-move-put-put场景。在现实中,情况实际上比这更糟,因为控制序列本身被发送到终端的多个字节所破坏:因此控制序列本身可能会被破坏!
在终端入口周围使用关键区域保护(lock
)。lock
应该包含所有必须是原子(不中断)的操作:
lock (foo) {
move(...)
draw(...)
}
适配WriteAt
功能
- 清空
- A被绘制到(E所在的位置)。
- E被清除(刚才画A的地方)。
- 绘制E。
使用上述方法,有可能(在特定时间)E将出现在屏幕上,而a将不出现。也就是说,lock
本身,在保护对控制台的访问时,不能充分保护线程和控制台之间的交互。
幸福的编码。
参见什么是常见的并发缺陷?查看一些一般提示和链接
前面关于锁定控制台访问权限的答案将解决您当前的问题。
你真的不需要显式线程。你可以用几个计时器和一些状态信息来做。例如:
class CharState
{
private static Random rnd = new Random();
private object RandomLock = new object();
public int x { get; private set; }
public int y { get; private set; }
public readonly char ch;
public CharState(char c)
{
ch = c;
SetRandomPos();
}
public void SetRandomPos()
{
lock (RandomLock)
{
// set x and y
}
}
}
随机数生成器在所有CharState
对象实例之间共享。它在SetRandomPos
中使用锁来保护,因为如果多个线程并发调用Random.Next
将失败。不要担心锁的"效率"。它可能会花费你100纳秒。
CharState
实例和计时器来控制它们:
CharState char1 = new CharState('A');
CharState char2 = new CharState('X');
System.Threading.Timer timer1 = new System.Threading.Timer(
MoveChar, char1, 1000, 1000);
System.Threading.Timer timer2 = new System.Threading.Timer(
MoveChar, char2, 1200, 1200);
这里,"A"将每秒移动一次,"X"将每1.2秒移动一次。
你的MoveChar
函数变成:
void MoveChar(object state)
{
CharState ch = (CharState)state;
// erase the previous position
WriteAt(" ", ch.x, ch.y);
ch.SetRandomPos();
WriteAt(ch.ch, ch.x, ch.y);
}
这种方法有很多好处。你不需要为你想要移动的每个角色使用单独的方法,你可以以不同的速率移动每个角色。如果你愿意,你可以扩展CharState
类,给每个角色一个特定的移动区域。
您可以使用显式线程来做同样的事情,但是计时器更容易使用,并且会消耗更少的系统资源。如果想移动10个不同的字符,就需要10个单独的线程,每个线程都会消耗系统上的大量资源。这并不好,特别是因为线程大部分时间都在睡觉——什么都不做。
另一方面,使用计时器,系统只在需要处理并发请求的线程数量时才会进行旋转。使用计时器,您可以有100个不同的字符移动,而系统将只使用少数线程。