【问题标题】:Cross Threading issue with C# ProgressBarC# ProgressBar 的跨线程问题
【发布时间】:2011-12-18 09:16:09
【问题描述】:

我正在创建一个倒计时计时器,允许用户选择一个有效时间,一旦倒计时归零,它就会播放声音。

我从System.Timers 命名空间实例化了一个计时器,并将其Interval 设置为1 秒。我将用户指定的时间转换为秒,并在每次Timer.Elapsed 函数命中时将该值递减1。一旦它达到零,这意味着倒计时已经到零,这意味着是时候播放声音了。

但是,只要它没有达到零,我就会将时间值减少1,并且我还想使用progressbar.Increment 函数增加ProgressBar

不幸的是,每当我这样做时,它都会给我一个与多线程问题有关的异常。我知道出了什么问题,但我不知道如何解决它。我需要在新线程上启动timer.Elapsed 函数吗?

错误是:

跨线程操作无效:已访问控件“CountdownProgress” 来自创建它的线程以外的线程。

此外,我们也欢迎任何有关更好编程习惯的提示。 非常感谢!

代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Timers;
using System.Media;
using System.Diagnostics;
using System.Threading;

namespace WindowsFormsApplication1
{
    public partial class Form_CDA : Form
    {
        public Form_CDA()
        {
            InitializeComponent();
        }

        private bool m_CountdownActive; // declare global variables and instantiate timer.
        private bool m_Cancelled;
        public decimal seconds;
        public decimal minutes;
        public decimal hours;
        public decimal time;
        public System.Timers.Timer timer = new System.Timers.Timer();

        private void Form_CDA_Load(object sender, EventArgs e)
        {
            m_Cancelled = false;
            timer.AutoReset = false;
            timer.Interval = 0;
            m_CountdownActive = false;
            richTextBox1.Text = "Hello, please select an hour between 0 and 100, a minute between 0 and 59, and a second between 0 and 59. The countdown timer will play a sound when finished.";
            btn_Cancel.Enabled = false;
            seconds = 0;
            minutes = 0;
            hours = 0;
            time = 0;
            m_StatusBar.Text = "Program properly loaded, waiting for user input"; // initialize variables.
        }

        private void btn_SetCountdown_Click(object sender, EventArgs e)
        {
            seconds = numUpDown_Seconds.Value;
            minutes = numUpDown_Minutes.Value;
            hours = numUpDown_Hours.Value;
            time = (hours * 3600) + (minutes * 60) + seconds; // calculate the total time in seconds.
            if (time != 0) // if time is not zero, start timer and set up event handler timer.elapsed.
            {
                timer.Interval = 1000;
                timer.AutoReset = true;
                timer.Start();
                timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
                CountdownProgress.Maximum = (int)time;
                CountdownProgress.Minimum = 0;
                CountdownProgress.Value = 0;
            }
            else
            {
                m_StatusBar.Text = "Invalid selection of times. Try again.";
                return;
            }
            DateTime dt = DateTime.Now;
            dt.AddSeconds((double)time);
            Label_Countdown.Text = "Finishing time: " + dt.ToString(); // display end time to user.
            m_CountdownActive = true;
            btn_Cancel.Enabled = true;
            btn_SetCountdown.Enabled = false;
            numUpDown_Hours.Enabled = false;
            numUpDown_Minutes.Enabled = false;
            numUpDown_Seconds.Enabled = false; // disable controls.
            m_Cancelled = true;
            m_StatusBar.Text = "Timer set to " + numUpDown_Hours.Value.ToString() + " hours, " + numUpDown_Minutes.Value.ToString() + " minutes, " + numUpDown_Seconds.Value.ToString() + " seconds.";
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (time == 0)
            {
                m_StatusBar.Text = "Countdown Finished";
                SoundPlayer soundPlayer = new SoundPlayer(@"C:\Users\marupakuuu\Desktop\New stuff\doorbell.wav");
                soundPlayer.Play(); // play sound.
                timer.Stop();
                return;
            }
            else
            {
                time = time - 1;
                CountdownProgress.Increment(1); // exception occurs here; multithreading issues.
            }
        }

        private void btn_Cancel_Click(object sender, EventArgs e)
        {
            // if user wishes to stop the countdown to start a new one.
            m_Cancelled = true;
            m_CountdownActive = false;
            btn_SetCountdown.Enabled = true;
            numUpDown_Seconds.Value = 0;
            numUpDown_Minutes.Value = 0;
            numUpDown_Hours.Value = 0;
            numUpDown_Hours.Enabled = true;
            numUpDown_Minutes.Enabled = true;
            numUpDown_Seconds.Enabled = true;
            btn_Cancel.Enabled = false;
            m_StatusBar.Text = "Countdown cancelled";
        }
    }
}

【问题讨论】:

标签: c# winforms multithreading progress-bar


【解决方案1】:

一个控件只能在它创建的线程中被访问。因此,使用Invoke 在创建控件(进度条)的主线程上作为委托执行给定代码。

void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    if (time == 0)
    {
        m_StatusBar.Text = "Countdown Finished";
        SoundPlayer soundPlayer = new SoundPlayer(@"C:\Users\marupakuuu\Desktop\New stuff\doorbell.wav");
        soundPlayer.Play(); // play sound.
        timer.Stop();
        return;
    }
    else
    {
        time = time - 1;
        Invoke(new Action(() => CountdownProgress.Increment(1)));
    }
}

您可能想阅读http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

【讨论】:

  • 非常感谢!工作完美。目前也在阅读这篇文章。杰出的! :)
  • @tf.rz 然而,真正的问题是为什么 CountdownProgress 在另一个线程上? WinForm Timer 控件在创建计时器的线程上运行事件。
  • @pst,见msdn.microsoft.com/en-us/library/… - 它说:If the SynchronizingObject property is Nothing, the Elapsed event is raised on a ThreadPool thread.
  • @ebb 好点。我正在考虑System.Windows.Forms.Timer,我建议将其用于一般的 UI 工作......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多