简体   繁体   English

C#中的线程问题

[英]Thread issue in C#

I am trying to generate a random fruit and display it on GUI in a label. 我正在尝试生成随机水果并将其显示在GUI中的标签中。 I am using this code to do it. 我正在使用此代码来做到这一点。

 partial class Form1 : Form
 {
    int MagicNumber = 0;
    List<string> NameList = new List<string>();
    Random r = new Random();

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        NameList.Add("Apples");
        NameList.Add("Pears");
        NameList.Add("Oranges");
        NameList.Add("Bananas");
        NameList.Add("Kiwi");

        for (int i = 0; i < 8; i++)
        {
            Thread t = new Thread(new ThreadStart(Display));
            t.Start();

            label1.Text = NameList[MagicNumber];
            Thread.Sleep(1000);
        }
   }

    private void Display()
    {
        MagicNumber = r.Next(5);
    }
}

The problem is the fact that in GUI i see only the last result of fruits choice and not how they are skipped from an iteration to other. 问题是这样的事实,在GUI中,我只能看到水果选择的最后结果,而看不到如何将它们从迭代中跳过到其他结果。 I thought that this code will give me the possibility to see how fruits changes until the last was chosen , when i is 8. 我以为这段代码将使我有可能看到水果的变化,直到我最后一个被选为8的时候。

Please if you have an idea why this code is not displaying how the fruits are chosen in label give me a hand ! 如果您有一个想法,为什么这个代码不显示标签中如何选择水果,请给我帮助!

Thanks. 谢谢。

You seem to be confusing timers and threads. 您似乎在混淆计时器和线程。 In this case, I think what you want is a timer; 在这种情况下,我认为您想要的是计时器; specifically, System.Windows.Forms.Timer . 特别是System.Windows.Forms.Timer You might do something like this: 您可能会执行以下操作:

partial class Form1 : Form
{
   Timer timer = new Timer();

   private void button1_Click(object sender, EventArgs e)
   {
      int i = 0;

      timer.Tick += (s, e) =>
      {
         if (i < 8)
         {
               label1.Text = nameList[r.Next(5)];
               i++;
         }
         else
            timer.Stop();
      };

      timer.Interval = 1000;
      timer.Start();
   }
}

The idea is that you set a timer to tick once a second, and then each time it ticks, you change the label and increment the counter until it reaches 8 -- at which point it stops. 这个想法是,您将计时器设置为每秒滴答一次,然后每次滴答时,您都要更改标签并递增计数器,直到达到8为止。 You always want to make sure you call Start() after you've set Tick and Interval ; 设置TickInterval 后,您始终要确保调用Start() otherwise, under some strange circumstances, the timer might tick before you have a chance to change the settings. 否则,在某些奇怪的情况下,计时器可能会在您有机会更改设置之前打勾。

Alternatively, you could use threading and Sleep(), in which case it might look like this: 或者,您可以使用threading和Sleep(),在这种情况下,它可能看起来像这样:

private void button1_Click(object sender, EventArgs e)
{
   Thread t = new Thread(new ThreadStart(Display));
   t.Start();
}

private void Display()
{
   for(int i = 0; i < 8; i++)
   {
      label1.Text = NameList[r.Next(5)];
      Thread.Sleep(1000);
   }
}

Thread.Sleep() always sleeps the thread that it's called from -- so maybe this is what you meant to do. Thread.Sleep()始终休眠从中调用它的线程-也许这就是您要执行的操作。

However, this might throw a thread synchronization exception -- Forms prevents you from accessing UI controls from another thread, since it might be in an invalid state (ie in the middle of rendering or doing something else that's volatile). 但是,这可能会引发线程同步异常-Forms阻止您从另一个线程访问UI控件,因为它可能处于无效状态(即在呈现过程中或执行其他易失性操作)。 System.Windows.Forms.Timer actually runs on the UI thread, so it's easier to manage. System.Windows.Forms.Timer实际上在UI线程上运行,因此更易于管理。

Your approach is flawed, but you may want to understand what is going on in your code, as it may help you find a better approach: 您的方法有缺陷,但是您可能希望了解代码中正在发生的事情,因为它可以帮助您找到更好的方法:

   for (int i = 0; i < 8; i++)
    {
        Thread t = new Thread(new ThreadStart(Display));
        t.Start();

        label1.Text = NameList[MagicNumber];
        Thread.Sleep(1000);
    }

You are looking through, creating eight threads every time the button is clicked. 您正在浏览,每次单击该按钮都会创建八个线程。 Do you have a reason to create eight threads? 您是否有理由创建八个线程? If so, you may want to create them once, inside your init function and reuse them. 如果是这样,您可能希望在init函数内部创建一次init用它们。

Then there is a race here in that your threads may not have had time to change MagicNumber before it is used, as the loop starts the threads then immediately changes the text, before going to sleep. 然后是一场竞赛,您的线程在使用MagicNumber之前可能没有时间更改它,因为循环启动线程然后立即更改文本,然后再进入睡眠状态。

The sleep is another problem, as you haven't gotten off of the main (event) thread, so the text isn't changed until you exit that event handler. 睡眠是另一个问题,因为您还没有离开主(事件)线程,因此文本不会更改,直到您退出该事件处理程序为止。

If you want to see the text changing, then you will need to get off of the main thread, and in a second thread go through and do the loop of eight. 如果要更改文本,则需要离开主线程,然后在第二个线程中进行八个循环。

Then, you can put that thread to sleep, and since the main thread was free to make the change you will see it. 然后,您可以将该线程置于休眠状态,并且由于主线程可以自由进行更改,因此您将看到它。

Here is an article from MS that is a bit dated, but the basic idea should help you: 这是一篇来自MS的文章,它有些陈旧,但是基本的想法应该可以为您提供帮助:

http://msdn.microsoft.com/en-us/magazine/cc188732.aspx http://msdn.microsoft.com/zh-CN/magazine/cc188732.aspx

Now you can use lambda expressions for your threads, as shown here: 现在,您可以为线程使用lambda表达式,如下所示:

http://www.rvenables.com/2009/01/threading-tips-and-tricks/ http://www.rvenables.com/2009/01/threading-tips-and-tricks/

Your observed problem of the form not refreshing is due to your function blocking the GUI thread and preventing a redraw of the window while its running. 您观察到的表单不刷新的问题是由于您的函数阻塞了GUI线程并阻止了窗口在运行时重绘。 And it's continuously running for 8 seconds. 而且它连续运行了8秒钟。 The GUI thread needs to handle messages to allow a window to be redrawn. GUI线程需要处理消息以允许重绘窗口。

But apart from what you observed it has has at least two theoretical problems related to threading: 但是除了您观察到的以外,它至少还存在两个与线程相关的理论问题:

  1. The read of MagicNumber isn't volatile, so the compiler may read it only once and cache the result. 对MagicNumber的读取不是易失的,因此编译器可能只读取一次并缓存结果。 It probably won't do that in practice since the code between each reading of the variable is so complicated that it can't guarantee that they won't affect the variable. 实际上,它可能不会这样做,因为每次读取变量之间的代码都很复杂,以致不能保证它们不会影响变量。
  2. r.Next isn't threadsafe. r.Next不是线程安全的。 So calling it from two different threads at the same time can corrupt the Random instance. 因此,从两个不同的线程同时调用它可能会破坏Random实例。 Won't happen in practice either since the delay is so long that one thread will most likely have finished before the next one starts. 在实践中也不会发生,因为延迟时间如此之长,以至于一个线程很可能在下一线程开始之前就已经完成。

The problem is that when you execute an event handler or a function called from it, the changes are rendered at the end. 问题在于,当您执行事件处理程序或从中调用的函数时,更改将在最后呈现。 Try changhing the label text inside the thread where you get the random number. 尝试在获取随机数的线程内更改标签文本。 You also have to set the CheckForIllegalCrossThreadCalls property to false in the form constructor. 您还必须在表单构造函数中将CheckForIllegalCrossThreadCalls属性设置为false。

Just call Application.DoEvents(); 只需调用Application.DoEvents(); after assigning text to label - that will refresh UI. 在为标签分配文本后-将刷新UI。

BTW I don't understand why you are using threads to generate random numbers 顺便说一句,我不明白为什么你要使用线程来生成随机数

There is a much better way to choose a random item: 有一种更好的选择随机项目的方法:

label1.Text = NameList.OrderBy(f => Guid.NewGuid()).First();

Randomizing on different threads is a bad idea in of itself. 在不同线程上随机化本身并不是一个好主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM