2011-09-16 17 views
3

いくつかの文字がコンソール上でランダムに動いていることを示すプログラムを実装したいと思います。C#でのマルチスレッドの使用に関する問題

コンソール上で1文字をランダムに移動する再帰的な方法を作成しました。私は2つの文字を移動する場合、同じメソッドを呼び出す2つのスレッドを使用します。

プログラムは最初の分が完璧に機能しますが、しばらくすると手紙はコンソールのどこにでも見え始めます!

私の再帰的メソッドは大丈夫です(私は再帰の代わりに(< 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); 
      } 
     } 
    } 
+4

コードを含めるのを忘れた –

+1

なぜ各文字に対して再帰的メソッドが必要なのか不明です。そのスタックはキャラクターが動くにつれて急速に成長します。 @ornが言ったように、いくつかのコードを投稿する –

答えて

5

スレッドで百万微妙な問題も発生することがあります - 共有リソースにアクセスするものが疑わしい考慮されなければならない

の移動位置に続く文字はではなく、1つのスレッドが別のスレッドを中断してmove-move-put-putシナリオを引き起こす可能性があることを考慮してください。実際には状況は実際に悪いこれはcontrol sequences自体が壊れたので複数のバイトが端末に送信されるため、制御シーケンス自体が壊れている可能性があります。

端末へのアクセスの周囲にクリティカルリージョンガード(lock)を使用してください。適切なWriteAt機能に適応

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

lockはお互いに関連して原子なければならないすべての操作を(中断せず)を包含するべきです。

しかし、検討し、この変更を行ってもまだ微妙な競合状態があることに注意してください:

  1. Aがクリアされます。
  2. Aが引かれます(ここでEはです)。
  3. Eがクリアされます(Aがちょうど描かれた場所でした)。
  4. が描かれる。

上記では、Aが表示されないうちにEが画面に表示される可能性があります。つまり、lock自体は、コンソールへのアクセスを保護しながら、スレッドとコンソール間のやり取りを適切に保護することができません。

ハッピーコーディング。


一部の一般的なヒントとリンクについては、What are common concurrency pitfalls?も参照してください。

1

コンソールへのアクセスをロックするという以前の回答は、あなたの直面する問題を解決します。

これには明示的なスレッドが必要ありません。いくつかのタイマーといくつかの状態情報でそれを行うことができます。たとえば、

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オブジェクトインスタンスすべてで共有されます。複数のスレッドが同時に呼び出すと、Random.Nextが失敗するため、ロックで保護されています(SetRandomPos)。ロックの "効率"を心配しないでください。 100ナノ秒かかるかもしれません。今

、それらを制御するための2つのインスタンス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); 

を、「」1秒に1回移動し、「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種類の異なる文字を動かすことができ、システムはほんの一握りのスレッドしか使用しません。

関連する問題