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

c#中使用多线程的问题

线程可能有无数微妙的问题——任何访问共享资源的都必须被认为是可疑的。

考虑一个move-position- after -a-put-character 不是原子的,一个线程可能会中断另一个线程,从而导致一个move-move-put-put场景。在现实中,情况实际上比这更糟,因为控制序列本身被发送到终端的多个字节所破坏:因此控制序列本身可能会被破坏!

在终端入口周围使用关键区域保护(lock)。lock应该包含所有必须是原子(不中断)的操作:

lock (foo) {
   move(...)
   draw(...)
}

适配WriteAt功能

然而,请记住,即使有了这个更改,仍然是一个微妙的竞争条件,考虑:
  1. 清空
  2. A被绘制到(E所在的位置)。
  3. E被清除(刚才画A的地方)。
  4. 绘制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个不同的字符移动,而系统将只使用少数线程。