C#后台工作者+计时器:交叉线程问题

时间:2012-08-21 20:25:55

标签: c# exception backgroundworker multithreading

背景

根据我收到的宝贵建议here我现在已将所有数据库密集型代码移至后台工作者,特别是直接调用数据库。该代码在backgroundworker的DoWork事件期间执行。如果在DoWork事件期间返回DataTable,我将该DataTable设置为类范围的变量。这样做是为了避免每次运行此代码时都必须调用需要DataTable的控件。

在执行该代码时,我有一个在主UI线程中更新的标签,让用户知道正在发生的事情。为了更新标签,我使用一个计时器,这样每隔750毫秒一个“。”被附加到标签的字符串。

我注意到的第一件事是后台工作者的RunWorkerCompleted事件没有触发。为了解决这个问题,我在每次打电话给后台工作人员之前做了Application.DoEvents();。它很难看,但它引发了事件。如果有人有办法解决这个问题,我全都听见了。

然后我遇到了一个有趣的困境。如果我在Visual Studio 2010中运行该程序,在调试模式下,我收到一个InvalidOperationException错误,指出“跨线程操作无效:控制'lblStatus'从其创建的线程以外的线程访问。”在backgroundworker的RunWorkerCompleted事件期间发生此错误,其中我在主UI线程中设置标签的文本。但是,当我通过可执行文件直接启动应用程序时,它完全按照需要工作(即标签的文本设置正确)。

问题

任何人都可以解释正在发生的事情/提出如何改进的建议吗?

代码

我无法发布所有涉及的代码,但这里有一些相关的东西:

namespace Test
{
    public partial class frmMain : Form
    {
        public static Boolean bStatus = false;
        static Boolean bTimer = false;
        System.Timers.Timer MyTimer = new System.Timers.Timer();

        public frmMain()
        {
            InitializeComponent();
            MyTimer.Elapsed += new System.Timers.ElapsedEventHandler(MyTimer_Elapsed);
            MyTimer.Interval = 750; // Every 3/4 of a second
            ExampleTrigger();
        }

        /// <Insert>Lots of unshown code here</Insert>

        private void ExampleTrigger()
        {
            // This is used to simulate an event that would require the backgroundworker
            Application.DoEvents();
            bgw.RunWorkerAsync(0);
            WaitText("Example - 1");
        }

        private static void MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            bTimer = true;
        }

        // Update status text
        private void WaitText(string txt)
        {
            MyTimer.Enabled = true;
            lblStatus.Text = txt;
            bStatus = false;
            while (!bStatus)
            {
                if (bTimer)
                {
                    txt = txt + ".";
                    lblStatus.Text = txt;
                    lblStatus.Update();
                    bTimer = false;
                }
            }
            MyTimer.Enabled = false;
        }

        private void bgw_DoWork(object sender, DoWorkEventArgs e)
        {
            int iSelect = (int)e.Argument;
            switch (iSelect)
            {
                case 0:
                    // Hit the database
                    break;
                /// <Insert>Other cases here</Insert>
                default:
                    // Do something magical!
                    break;
            }
        }
        private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            bStatus = true;
            lblStatus.Text = "Ready!";  // This is where the exception occurs!
        }
    }
}

1 个答案:

答案 0 :(得分:0)

永远不要像在UI线程中那样运行while()循环 您将冻结UI,直到循环终止;这违背了目的。

此外,System.Timers.Timer不会在UI线程中运行回调 请改用WinForms Timer。

切换到WinForms计时器后,您只需附加到计时器回调中的标签,并在操作完成时禁用计时器。