C# C中的线程问题#

C# C中的线程问题#,c#,multithreading,C#,Multithreading,我试图生成一个随机的水果,并在GUI上以标签的形式显示它。我正在使用这个代码来完成它 partial class Form1 : Form { int MagicNumber = 0; List<string> NameList = new List<string>(); Random r = new Random(); public Form1() { InitializeComponent(); }

我试图生成一个随机的水果,并在GUI上以标签的形式显示它。我正在使用这个代码来完成它

 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);
    }
}
部分类表单1:表单
{
int MagicNumber=0;
列表名称列表=新列表();
随机r=新随机();
公共表格1()
{
初始化组件();
}
私有无效按钮1\u单击(对象发送者,事件参数e)
{
姓名列表。添加(“苹果”);
姓名列表。添加(“梨”);
姓名列表。添加(“橙子”);
姓名列表。添加(“香蕉”);
姓名列表。添加(“猕猴桃”);
对于(int i=0;i<8;i++)
{
线程t=新线程(新线程开始(显示));
t、 Start();
label1.Text=名称列表[MagicNumber];
睡眠(1000);
}
}
专用void Display()
{
MagicNumber=r.Next(5);
}
}
问题在于,在GUI中,我只看到选择结果的最后一个结果,而没有看到它们是如何从一个迭代跳到另一个迭代的。我想这段代码会让我有可能看到水果是如何变化的,直到最后一个被选中,当我8岁的时候

请如果你有一个想法,为什么这个代码没有显示如何选择的水果标签给我一只手


谢谢。

有一种更好的方法来选择随机项目:

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

在不同线程上随机化本身就是一个坏主意。

您观察到的表单不刷新问题是由于您的函数阻塞了GUI线程,并阻止了窗口在运行时重新绘制。它连续运行了8秒。GUI线程需要处理消息以允许重新绘制窗口

但除了你观察到的,它至少有两个与线程相关的理论问题:

  • MagicNumber的读取不是易变的,因此编译器可能只读取一次并缓存结果。实际上,它可能不会这样做,因为每次读取变量之间的代码非常复杂,无法保证它们不会影响变量
  • r.Next
    不是线程安全的。因此,同时从两个不同的线程调用它可能会损坏
    Random
    实例。在实践中也不会发生,因为延迟太长,一个线程很可能在下一个线程启动之前完成

  • 问题在于,当您执行事件处理程序或从中调用的函数时,更改会在最后呈现。尝试在线程内更改标签文本,从中获得随机数。您还必须在表单构造函数中将CheckForIllegalCrossThreadCalls属性设置为false。

    您似乎混淆了计时器和线程。在这种情况下,我想你想要的是一个计时器;明确地您可以这样做:

    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();
       }
    }
    
    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()
    总是使调用它的线程处于睡眠状态——因此,也许这就是您想要做的


    但是,这可能会引发线程同步异常——窗体阻止您访问来自另一个线程的UI控件,因为它可能处于无效状态(即在渲染的中间或做其他易失性的操作)。System.Windows.Forms.Timer实际上是在UI线程上运行的,因此更易于管理。

    您的方法有缺陷,但您可能需要了解代码中的情况,因为它可能会帮助您找到更好的方法:

       for (int i = 0; i < 8; i++)
        {
            Thread t = new Thread(new ThreadStart(Display));
            t.Start();
    
            label1.Text = NameList[MagicNumber];
            Thread.Sleep(1000);
        }
    
    for(int i=0;i<8;i++)
    {
    线程t=新线程(新线程开始(显示));
    t、 Start();
    label1.Text=名称列表[MagicNumber];
    睡眠(1000);
    }
    
    您正在浏览,每次单击按钮时创建八个线程。你有理由创建八个线程吗?如果是这样,您可能希望在
    init
    函数中创建它们一次并重用它们

    然后这里有一个竞赛,你的线程在使用之前可能没有时间更改
    MagicNumber
    ,因为循环启动线程,然后在进入睡眠之前立即更改文本

    睡眠是另一个问题,因为您还没有脱离主(事件)线程,所以文本在退出该事件处理程序之前不会更改

    如果你想看到文本的变化,那么你需要离开主线程,在第二个线程中完成八次循环

    然后,您可以将该线程置于睡眠状态,因为主线程可以自由进行更改,您将看到它

    以下是MS的一篇文章,有点过时,但其基本思想应该对您有所帮助:

    现在可以为线程使用lambda表达式,如下所示:


    只需调用
    Application.DoEvents()将文本指定给标签后-这将刷新UI


    顺便说一句,我不明白你为什么要使用线程生成随机数

    你只在一个单独的线程中生成随机数(访问时带有竞态条件),你用线程锁定UI。睡眠只会看到最后的结果,你需要在Display()中移动代码然后你必须调用Control.Invoke从新线程访问UI线程看起来你在尝试C风格的线程,这在C中是不同的,启动一个新线程只是为了设置一个int的值是非常非常非常无效的。你知道NewGuid的性能成本吗??为什么不只是随机的?这项技术来自杰夫·阿特伍德。这项技术可能来自Donald Knuth,它仍然是愚蠢的,而且毫无理由的昂贵。在Jeff的原始示例中,它比这里更有意义。因为写一个正确的洗牌并不是那么容易。但是我仍然会使用加密PRNG,而不是创建guid来获取不可预测的数字。而且我在
    NewGuid
    的文档中没有看到有关如何生成guid的指示。虽然第4类GUI是随机的,但还有其他不会产生随机数的GUI生成方法。您只能从GUI线程更改标签文本。因为你可以禁用异常警告