【问题标题】:Simple Thread Programming简单的线程编程
【发布时间】:2010-05-06 19:27:37
【问题描述】:

我已经开始使用 c# 中的线程,但现在需要帮助,这是我的代码:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        DoCount();
    }
    public void DoCount()
    {
        for (int i = 0; i < 100; i++)
        {
            objTextBox.Text = i.ToString();
            Thread.Sleep(100);
        }
    }
}

它是一个带有文本框的简单获胜表格,我想查看“计数”,但正如您在我的代码中看到的那样,文本框显示我 99,它一直计数到 99,然后显示.. 我会想,我必须在一个新线程中管理它,但不知道如何!

【问题讨论】:

  • “简单”和“线程”很少彼此相邻...
  • 像“简单线程编程”这样的句子就像一本书“用9个简单的步骤构建航天飞机”。
  • @MarcGravell -- 初学者的问题除外:-P

标签: c# multithreading winforms


【解决方案1】:

使用BackgroundWorker。 MSDN 上有一个BackgroundWorker overview

以下是您的代码外观示例:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker backgroundWorker = (BackgroundWorker)sender;
        for (int i = 0; i < 100; i++)
        {
            backgroundWorker.ReportProgress(i);
            Thread.Sleep(100);
        }
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        textBox1.Text = e.ProgressPercentage.ToString();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }
}

其他说明:

【讨论】:

  • 您可以使用 BackgroundWorker 或 Thread 或(如我的示例中)ThreadPool,但您仍然需要处理InvokeRequired 的问题。 BackgroundWorker 有两个事件可用于设置 UI 控件,但如果您有多个字段,这不是一个非常灵活的解决方案。
  • 感谢您添加示例。现在很明显,使用 ProgressChanged 来“走私”字符串值是可行的,但很难扩展。考虑后台线程必须更新多个字段的情况。
【解决方案2】:

这可能是您正在寻找的:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        DoCount();
    }
    public void DoCount()
    {
        Thread t = new Thread(new ThreadStart(delegate
        {
           for (int i = 0; i < 100; i++)
           {
               this.Invoke((Action) delegate { objTextBox.Text = i.ToString(); });
               Thread.Sleep(1000);
           }
        }));
        t.IsBackground = true;
        t.Start();
    }
}

备注

  1. 使用基本的 Thread 而不是 BackgroundWorker
  2. 使用 Invoke 更新 UI 线程上的文本框
  3. IsBackground 设置为 true,以便在循环完成之前关闭窗体时程序退出。

【讨论】:

  • 这行得通。您应该检查 InvokeRequired 还是我们可以确定它总是正确的?
  • 这应该总是正确的,因为我们总是在一个新线程上。永远不会出现内部线程与外部 (UI) 线程相同的情况。
【解决方案3】:

您可能想尝试使用SynchronizationContext 来执行此操作。

这是我不久前整理的一个简单示例:

public partial class Form1 : Form
{
    private SynchronizationContext c;
    private Thread t;
    private EventWaitHandle pause =
        new EventWaitHandle(false, EventResetMode.ManualReset);

    public Form1()
    {
        this.InitializeComponent();
        this.c = SynchronizationContext.Current;
    }

    private void Form1Activated(object sender, EventArgs e)
    {
        this.t = new Thread(new ThreadStart(delegate
        {
            this.pause.Reset();
            while (this.t.IsAlive && !this.pause.WaitOne(1000))
            {
                this.c.Post(
                    state => this.label1.Text = DateTime.Now.ToString(),
                    null);
            }
        }));
        this.t.IsBackground = true;
        this.t.Start();
    }

    private void Form1Deactivate(object sender, EventArgs e)
    {
        this.pause.Set();
        this.t.Join();
    }

    /// <summary>
    /// Button1s the click.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    private void Button1Click(object sender, EventArgs e)
    {
        this.Close();
    }
}

【讨论】:

  • 这可行,但同步上下文可能是最复杂的方法。
【解决方案4】:

您根本不需要线程来执行此类操作 - 考虑将您的代码更改为事件驱动并使用 System.Windows.Forms.Timer 对象来实现您的计时。为此使用计时器有一个巨大的优势 - 它不需要 1MB 的内存(线程会),而且您不需要同步它们 - windows 会为您完成。

【讨论】:

  • 取决于他要做什么,这从令牌示例中不清楚。如果他只是想定期增加计数,那么 Timer 可能没问题。如果他想在后台做长时间的工作,那就不行。
【解决方案5】:

在 Thread.Sleep 之后,试试这个:

this.Update();

【讨论】:

  • 这将更新窗口,但它不会处理用户输入,并且(不希望完全混淆 OP,但必须说)它仍然会阻止 DDE 广播 100 秒以启动excel 速度很慢,它仍然会阻止您关闭 PC,直到应用程序完成.....以及所有其他原因,您不能在不发送消息的情况下阻止您的 UI 线程。
【解决方案6】:

不要直接拨打DoCount,拨打ThreadPool.QueueUserWorkItem(DoCount)。这将在新线程中运行 DoCount。 (还有其他方法也可以,但这是最简单的方法。)

下一个问题是不能直接从线程中设置文本框。

要解决这个问题,请使用类似的代码:

if (this.textBox1.InvokeRequired)
{   
    SetTextCallback d = new SetTextCallback(SetText);
    this.Invoke(d, new object[] { text });
}

有关完整示例,请参阅 http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx

【讨论】:

    【解决方案7】:

    我的解决方案与 Mark 的解决方案几乎相同。唯一的区别是我在我的 ProgressChanged 事件中检查了 InvokeRequired。这是我的示例代码:

    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Forms;
    
    namespace tester
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                if (!backgroundWorker1.IsBusy)
                    backgroundWorker1.RunWorkerAsync();
            }
    
            /// <summary>
            /// This delegate enables asynchronous calls for setting the text property on a control.
            /// </summary>
            delegate void SetTextCallback(string status);
    
            private void BackgroundWorker1DoWork(object sender, DoWorkEventArgs e)
            {
                for (var i = 0; i < 100; i++)
                {
                    backgroundWorker1.ReportProgress(i);
                    Thread.Sleep(100);
                }
            }
    
            private void BackgroundWorker1ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                if (label1.InvokeRequired)
                    Invoke(new SetTextCallback(SetLabelText), new object[] { e.ProgressPercentage.ToString()});
                else
                    SetLabelText(e.ProgressPercentage.ToString());
            }
    
            private void SetLabelText(string text)
            {
                label1.Text = text;
            }
        }
    }
    

    【讨论】:

    • @Chuck:如果我理解正确的话,InvokeRequired 永远不会为真,因为 BackgroundWorker 在主线程中调用了进度更改事件。
    【解决方案8】:

    多线程可以解决这个问题,但对于像这个计数器这样简单的东西,它是不必要的。

    另一个用户推荐了 this.Update()。这可以使数字出现,因为 UI 将自行重绘。但它并没有解决窗口没有响应的事实(你不能移动它)。

    第三个解决方案和我对这个特定程序的建议是 Application.DoEvents()。它的作用是告诉底层原生窗口在消息池上执行其 ProcessMessages 方法。消息池包含当窗口需要重绘、鼠标移动、窗体已移动、最小化等时 Windows 发送给它的事件消息。这些指令由 Windows 发送并已排队。问题是程序在 UI 空闲之前不会处理它们。你可以通过调用这个方法来强制它。

    Application.DoEvents() 将产生一个以 100 毫秒间隔按预期响应的窗口。它可能有点不稳定(线程会更灵敏),但它很容易放入并且通常就足够了。

    for (int i = 0; i < 100; i++)
    {
        objTextBox.Text = i.ToString();
        Application.DoEvents();
        Thread.Sleep(100);
    }
    

    【讨论】:

    • 这是错误的,原因与更新错误的原因相同。只是需要更长的时间才能看到它是错误的。如果您打算像这样在单个线程上执行此操作,请使用事件驱动模型和计时器,这样您就可以保持消息泵处于活动状态。
    • @Stewart,我的演示代码调用了调用消息泵的 Application.DoEvents()。 .Update() 没有。如果您不同意,请尝试一下。一个计时器很好,但你会在计时器事件中放入什么来保持消息泵的活动?
    【解决方案9】:

    这是我的一个简单示例:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            Action countUp = this.CountUp;
            countUp.BeginInvoke(null, null);
        }
    
        private void CountUp()
        {
            for (int i = 0; i < 100; i++)
            {
                this.Invoke(new Action<string>(UpdateTextBox), new object[] { i.ToString() });
                Thread.Sleep(100);
            }
        }
    
        private void UpdateTextBox(string text)
        {
            this.textBox1.Text = text;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-10
      • 1970-01-01
      • 1970-01-01
      • 2013-11-22
      • 2011-09-08
      • 2010-10-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多