【问题标题】:Problems using multithreading on C#在 C# 上使用多线程的问题
【发布时间】:2011-09-16 15:53:12
【问题描述】:

我想实现一个程序,在控制台上显示一些随机移动的字符,每个字符都有不同的速度。

我创建了一种递归方法,可以在控制台上随机移动一个字母。当我想移动两个字母时,我使用两个线程调用相同的方法。

该程序在最初的几分钟内运行良好,但过了一段时间后,这些字母开始在控制台上到处出现!

我真的很确定我的递归方法没问题(我什至尝试创建另一种方法,这次只是使用 while(i

非常感谢。

编辑:对不起,这是一个示例代码(不要考虑如果字母处于相同位置会发生什么)。字母在“体育场”上移动,它们在 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);
            }
        }
    }

【问题讨论】:

  • 您忘记包含代码
  • 不清楚为什么每个字符都需要递归方法。随着角色移动,该堆栈将快速增长。正如@orn 所说,发布一些代码

标签: c# multithreading recursion console


【解决方案1】:

线程可能存在一百万个微妙的问题——任何访问共享资源的东西都必须被视为可疑。

考虑一个移动位置跟随一个放置字符不是原子的,一个线程可能会中断另一个线程,从而导致移动移动放置场景。实际上,情况实际上比这更糟,因为control sequences 本身被发送到终端的多个 字节所破坏:因此控制序列本身可能正在损坏!

在终端访问周围使用关键区域保护 (lock)。 lock 应该包含所有相对于彼此必须是原子(不中断)的操作:

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

酌情适应WriteAt函数。

但是,请记住,即使进行了此更改,仍然存在微妙的竞争条件,请考虑:

  1. A 被清除。
  2. A 被绘制(到 E 所在的位置)。
  3. E 被清除(是刚刚绘制 A 的位置)。
  4. E 已绘制。

根据上述情况,(在特定时间)E 可能会出现在屏幕上,而 A 不会出现。也就是说,lock 本身在保护对控制台的访问的同时,无法充分保护线程和控制台之间的交互。

编码愉快。


有关一些一般提示和链接,另请参阅 What are common concurrency pitfalls?

【讨论】:

    【解决方案2】:

    先前关于锁定控制台访问的答案将解决您的直接问题。

    你真的不需要显式线程。您可以使用几个计时器和一些状态信息来完成。例如:

    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 个不同的角色四处移动,而系统将只使用少数线程。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-21
      • 2021-09-03
      • 1970-01-01
      • 2017-07-15
      • 1970-01-01
      相关资源
      最近更新 更多