5

我正在尝试开发的表格有一组 6 个图片框和一组 6 个芯片图像。我有一个按钮,单击该按钮需要创建 6 个线程来“滚动”骰子,显示每个图像片刻。我遇到的问题是我需要在掷骰子后在按钮单击中调用一个方法。我可以掷骰子,但会立即显示消息框。我尝试了几种不同的方法并得到了各种错误。在下面的非工作版本中,程序冻结。我已经检查了大量资源,但我只是没有很好地掌握一些概念,比如 Delegates 和 Invoke。任何帮助都会很棒!这是我的程序

namespace testDice
{
    public partial class Form1 : Form
    {
        private Image[] imgAr;
        private PictureBox[] picBoxAr;
        private Random r;
        private Thread[] tArray;
        private ThreadStart tStart;
        private delegate void setTheImages();

        public Form1()
        {
            InitializeComponent();
            setImageArray();
            setPicBoxAr();
        }

        private void setImageArray()
        {
            imgAr = new Image[6];
            imgAr[0] = testDice.Properties.Resources.die6;
            imgAr[1] = testDice.Properties.Resources.die1;
            imgAr[2] = testDice.Properties.Resources.die2;
            imgAr[3] = testDice.Properties.Resources.die3;
            imgAr[4] = testDice.Properties.Resources.die4;
            imgAr[5] = testDice.Properties.Resources.die5;

        }

        private void setPicBoxAr()
        { 
            picBoxAr = new PictureBox[6];
            picBoxAr[0] = pictureBox1;
            picBoxAr[1] = pictureBox2;
            picBoxAr[2] = pictureBox3;
            picBoxAr[3] = pictureBox4;
            picBoxAr[4] = pictureBox5;
            picBoxAr[5] = pictureBox6;
        }   

        private void button1_Click(object sender, EventArgs e)
        {
            roll();

            //wait for threads to finish and update images--doesn't work
            for (int n = 0; n < 6; n++)
            {
                while (tArray[n].IsAlive)
                {
                    for (int i = 0; i < 6; i++)
                    {
                        this.picBoxAr[i].Update();
                    }
                }
            }

            MessageBox.Show("Each die has its own thread");
        }

        private void roll()
        {
            this.tStart = new ThreadStart(RunAllDiceThreads);
            this.tArray = new Thread[6];
            for (int i = 0; i < 6; i++)
            {
                this.tArray[i] = new Thread(tStart);
                this.tArray[i].Start();
            }
        }

        private void RunAllDiceThreads()
        {  
            int n = 0;
            while (n < 50)
            {
                setImg();
                Thread.Sleep(50);
                n++;
            }

            for (int i = 0; i < 6; i++)
            {
                if (tArray[i] != null)
                {
                    tArray[i].Abort();
                    tArray[i] = null;
                }
            }
        }// end RunAllDiceThreads

        private void setImg()
        {
            r = new Random();

            for (int i = 0; i < 6; i++)
            {
                    if (this.picBoxAr[i].InvokeRequired)
                    {
                        setTheImages s = new setTheImages(setImg);
                        // parameter mismatch error here
                        //this.Invoke(s, new object[] { imgAr[r.Next(6)] }); 
                        //Freezes here!!
                          this.Invoke(s);
                    }
                    else
                    {
                        this.picBoxAr[i].Image = imgAr[r.Next(6)];
                    }
            }
        }//end setImg

   }// end class Form1
}//end namespace testDice
4

3 回答 3

2

听起来您在调用设置图像和更新图片框之间遇到了僵局。

我建议重新考虑你的程序。您的程序几乎似乎是建立在您使用单个线程对单个模具进行建模的概念之上的。将模具的状态与线程的状态分开。例如,您可能想要创建一个具有特定状态的 Die 类,例如 IsRolling 或 CurrentValue。在工作线程的循环内使用和修改该类的对象(仅限该类)。这样,您就不必调用回您的 UI 线程来更新。这样,依赖关系就干净多了。您可能想创建一个计时器在您的 UI 线程中定期触发(例如每秒 10-30 次),读取每个骰子的状态,并以这种方式更新图像。就死锁而言,这要安全得多,因为您没有任何循环依赖关系。它还可能会产生一个更有吸引力的界面,因为您的模具图像将以更流畅、更可预测的方式更新。

另一个经验法则...不要调用 Thread.Abort()(请参阅参考资料)。使用 Die 对象的属性并简单地从中读取以更新 UI 通常会更安全。

于 2012-12-06T01:26:52.210 回答
0

您需要MessageBox.Show("Each die has its own thread");button1_Click.

创建一个属性来跟踪返回的线程数。当它达到 6 调用时MessageBox.Show("Each die has its own thread");(您可能希望将此调用放在它自己的方法中并调用该方法)。

您的问题是您正在启动线程,然后在它们运行时显示消息框而不是等待线程返回。

于 2012-12-06T01:27:14.770 回答
-1

如果您能够使用最新版本的 .Net Framework,我建议您使用System.Threading.Tasks命名空间。好处是它封装了很多多线程细节,让事情变得更干净。这是一个简单的例子。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TasksExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // holds all the tasks you're trying to run
            List<Task> waitingTasks = new List<Task>();

            // a simple object to lock on
            object padlock = new object();

            // simple shared value that each task can access
            int sharedValue = 1;

            // add each new task to the list above.  The best way to create a task is to use the Task.Factory.StartNew() method.
            // you can also use Task.Factory<RETURNVALUE>.StartNew() method to return a value from the task
            waitingTasks.Add(Task.Factory.StartNew(() => 
            {
                // this makes sure that we don't enter a race condition when trying to access the
                // shared value
                lock (padlock)
                {
                    // note how we don't need to explicitly pass the sharedValue to the task, it's automatically available
                    Console.WriteLine("I am thread 1 and the shared value is {0}.", sharedValue++);
                }
            }));

            waitingTasks.Add(Task.Factory.StartNew(() => 
            {
                lock (padlock)
                {
                    Console.WriteLine("I am thread 2 and the shared value is {0}.", sharedValue++);
                }
            }));

            waitingTasks.Add(Task.Factory.StartNew(() => 
            {
                lock (padlock)
                {
                    Console.WriteLine("I am thread 3 and the shared value is {0}.", sharedValue++);
                }
            }));

            waitingTasks.Add(Task.Factory.StartNew(() => 
            {
                lock (padlock)
                {
                    Console.WriteLine("I am thread 4 and the shared value is {0}.", sharedValue++);
                }
            }));

            waitingTasks.Add(Task.Factory.StartNew(() => 
            {
                lock (padlock)
                {
                    Console.WriteLine("I am thread 5 and the shared value is {0}.", sharedValue++);
                }
            }));

            waitingTasks.Add(Task.Factory.StartNew(() => 
            {
                lock (padlock)
                {
                    Console.WriteLine("I am thread 6 and the shared value is {0}.", sharedValue++);
                }
            }));


            // once you've spun up all the tasks, pass an array of the tasks to Task.WaitAll, and it will
            // block until all tasks are complete
            Task.WaitAll(waitingTasks.ToArray());

            Console.WriteLine("Hit any key to continue...");
            Console.ReadKey(true);
        }
    }
}

我希望这会有所帮助,如果您需要更多帮助,请告诉我。

于 2012-12-06T01:31:28.030 回答