【问题标题】:Why the timer tick event in the new class call a method from form1 once and in the next time it does nothing?为什么新类中的计时器滴答事件会从 form1 调用一次方法,而下一次它什么也不做?
【发布时间】:2014-07-15 16:13:12
【问题描述】:

在 form1 我有这个公共方法:

public void CombindedStringFix()
        {
            BeginUpdate();

            label3.Text = SaveOldHtml.HtmlLoadedFileNumber.ToString();
            richTextBox1.Clear();
            List<string> newText1 = new List<string>();
            label1.Select();
            scrollerList = new List<string>(Filters.newTextWithoutLinks);
            scrollerText = string.Join(Environment.NewLine, scrollerList);
            scroller1.TextToScroll = scrollerText;
            combindedString = string.Join(Environment.NewLine, SaveOldHtml.newText);
            richTextBox1.Text = combindedString;            
            string[] rlines = richTextBox1.Lines;
            timer3.Start();
            richTextBox1.SelectionStart = 0;
            richTextBox1.SelectionLength = rlines[0].Length;
            richTextBox1.SelectionColor = Color.Red;
            richTextBox1.Select(rlines[0].Length, rlines[1].Length + 1);
            richTextBox1.SelectionColor = Color.Green;

            EndUpdate();
        }

然后在新类中我调用这个方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Timers;
using System.Windows.Forms;

namespace ScrollLabelTest
{

    class SaveOldHtml
    {
        private static Form1 frm1 = null;
        private static int count;
        private static System.Timers.Timer _timer = new System.Timers.Timer();
        private static string page;
        public static List<string> newText = new List<string>();
        public static int HtmlLoadedFileNumber = 0;

        public SaveOldHtml(string DirectoryToSave,int count, string contents)
        {
            System.IO.File.WriteAllText(DirectoryToSave + "Page" + count.ToString("D6")
                                        + ".html", contents);
        }

        public SaveOldHtml(string DirectoryToSave, List<string> newTextList, int count)
        {
            using (StreamWriter myStream = new StreamWriter(DirectoryToSave + "newTextList" + count.ToString("D6")
                                        + ".txt"))
            {
                for (int i = 0; i < newTextList.Count; i++)
                {
                    myStream.WriteLine(newTextList[i]);
                }

            }
        }

        public static void Start(Form1 form)
        {
            frm1 = form;
            _timer.Elapsed += _timer_Elapsed;
            _timer.Interval = 10000;
            count = 5;
            LoadOldHtmlFiles();
            frm1.CombindedStringFix();
            _timer.Start();
        }

        static void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            _timer.Stop();
            LoadOldHtmlFiles();
            frm1.CombindedStringFix();
            _timer.Start();
        }

        private static void LoadOldHtmlFiles()
        {

            page = File.ReadAllText(@"c:\temp\OldHtml\page" + count.ToString("D6") + ".html");
            ListsExtractions.OffExtractions(@"c:\temp\OldHtml\page" + count.ToString("D6") + ".html", page, newText);
            count ++;
            HtmlLoadedFileNumber++;
        }
    }
}

一旦我在类的 Start 方法中调用 CombindedStringFix 方法,它就会工作并执行 CombindedStringFix 中的所有行。我使用了一个断点,它可以完成所有工作。

但是下次当它在新类中调用计时器滴答事件中的方法时,我使用了一个断点,并且一旦它执行第一行 BegingUpdate();它只是继续,永远不会在计时器滴答事件中再次停止,并且永远不会在 BeginUpdate() 之后继续;

即使我删除 BeginUpdate();所以它会做第一行然后继续它永远不会移动到下一行。

我现在看到我在计时器滴答事件中遇到异常:

跨线程操作无效:控件“Form1”从创建它的线程以外的线程访问

System.InvalidOperationException was caught
  HResult=-2146233079
  Message=Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.
  Source=System.Windows.Forms
  StackTrace:
       at System.Windows.Forms.Control.get_Handle()
       at ScrollLabelTest.Form1.BeginUpdate() in e:\scrolllabel\ScrollLabel\ScrollLabel\Form1.cs:line 69
       at ScrollLabelTest.Form1.CombindedStringFix() in e:\scrolllabel\ScrollLabel\ScrollLabel\Form1.cs:line 280
       at ScrollLabelTest.SaveOldHtml._timer_Elapsed(Object sender, ElapsedEventArgs e) in e:\scrolllabel\ScrollLabel\ScrollLabel\SaveOldHtml.cs:line 64
  InnerException: 

我猜这与 frm1 变量有关。

【问题讨论】:

    标签: c# .net winforms


    【解决方案1】:

    问题很可能是在您的 Elapsed 事件中某处发生了异常,并且计时器永远不会再次启动。您可以设置一个断点并在它第一次进入时跟随它以查看它在哪里/如果它失败。或者,您可以对其进行编码以启动备份:

    static void _timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        _timer.Stop();
    
        try
        {
            LoadOldHtmlFiles();
            frm1.CombindedStringFix();
        }
        catch (Exception ex)
        {
            // do something (or nothing) with the exception
        }
        finally 
        {
            _timer.Start();
        }
    }
    

    既然您确认发生了跨线程异常,我建议您阅读:

    How to: Make Thread-Safe Calls to Windows Forms Controls

    这是他们的演示,以防链接断开:

    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Forms;
    
    namespace CrossThreadDemo
    {
        public class Form1 : Form
        {
            // This delegate enables asynchronous calls for setting 
            // the text property on a TextBox control. 
            delegate void SetTextCallback(string text);
    
            // This thread is used to demonstrate both thread-safe and 
            // unsafe ways to call a Windows Forms control. 
            private Thread demoThread = null;
    
            // This BackgroundWorker is used to demonstrate the  
            // preferred way of performing asynchronous operations. 
            private BackgroundWorker backgroundWorker1;
    
            private TextBox textBox1;
            private Button setTextUnsafeBtn;
            private Button setTextSafeBtn;
            private Button setTextBackgroundWorkerBtn;
    
            private System.ComponentModel.IContainer components = null;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }
    
            // This event handler creates a thread that calls a  
            // Windows Forms control in an unsafe way. 
            private void setTextUnsafeBtn_Click(
                object sender, 
                EventArgs e)
            {
                this.demoThread = 
                    new Thread(new ThreadStart(this.ThreadProcUnsafe));
    
                this.demoThread.Start();
            }
    
            // This method is executed on the worker thread and makes 
            // an unsafe call on the TextBox control. 
            private void ThreadProcUnsafe()
            {
                this.textBox1.Text = "This text was set unsafely.";
            }
    
            // This event handler creates a thread that calls a  
            // Windows Forms control in a thread-safe way. 
            private void setTextSafeBtn_Click(
                object sender, 
                EventArgs e)
            {
                this.demoThread = 
                    new Thread(new ThreadStart(this.ThreadProcSafe));
    
                this.demoThread.Start();
            }
    
            // This method is executed on the worker thread and makes 
            // a thread-safe call on the TextBox control. 
            private void ThreadProcSafe()
            {
                this.SetText("This text was set safely.");
            }
    
            // This method demonstrates a pattern for making thread-safe 
            // calls on a Windows Forms control.  
            // 
            // If the calling thread is different from the thread that 
            // created the TextBox control, this method creates a 
            // SetTextCallback and calls itself asynchronously using the 
            // Invoke method. 
            // 
            // If the calling thread is the same as the thread that created 
            // the TextBox control, the Text property is set directly.  
    
            private void SetText(string text)
            {
                // InvokeRequired required compares the thread ID of the 
                // calling thread to the thread ID of the creating thread. 
                // If these threads are different, it returns true. 
                if (this.textBox1.InvokeRequired)
                {   
                    SetTextCallback d = new SetTextCallback(SetText);
                    this.Invoke(d, new object[] { text });
                }
                else
                {
                    this.textBox1.Text = text;
                }
            }
    
            // This event handler starts the form's  
            // BackgroundWorker by calling RunWorkerAsync. 
            // 
            // The Text property of the TextBox control is set 
            // when the BackgroundWorker raises the RunWorkerCompleted 
            // event. 
            private void setTextBackgroundWorkerBtn_Click(
                object sender, 
                EventArgs e)
            {
                this.backgroundWorker1.RunWorkerAsync();
            }
    
            // This event handler sets the Text property of the TextBox 
            // control. It is called on the thread that created the  
            // TextBox control, so the call is thread-safe. 
            // 
            // BackgroundWorker is the preferred way to perform asynchronous 
            // operations. 
    
            private void backgroundWorker1_RunWorkerCompleted(
                object sender, 
                RunWorkerCompletedEventArgs e)
            {
                this.textBox1.Text = 
                    "This text was set safely by BackgroundWorker.";
            }
    
            #region Windows Form Designer generated code
    
            private void InitializeComponent()
            {
                this.textBox1 = new System.Windows.Forms.TextBox();
                this.setTextUnsafeBtn = new System.Windows.Forms.Button();
                this.setTextSafeBtn = new System.Windows.Forms.Button();
                this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();
                this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
                this.SuspendLayout();
                //  
                // textBox1 
                //  
                this.textBox1.Location = new System.Drawing.Point(12, 12);
                this.textBox1.Name = "textBox1";
                this.textBox1.Size = new System.Drawing.Size(240, 20);
                this.textBox1.TabIndex = 0;
                //  
                // setTextUnsafeBtn 
                //  
                this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55);
                this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";
                this.setTextUnsafeBtn.TabIndex = 1;
                this.setTextUnsafeBtn.Text = "Unsafe Call";
                this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click);
                //  
                // setTextSafeBtn 
                //  
                this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);
                this.setTextSafeBtn.Name = "setTextSafeBtn";
                this.setTextSafeBtn.TabIndex = 2;
                this.setTextSafeBtn.Text = "Safe Call";
                this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
                //  
                // setTextBackgroundWorkerBtn 
                //  
                this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55);
                this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";
                this.setTextBackgroundWorkerBtn.TabIndex = 3;
                this.setTextBackgroundWorkerBtn.Text = "Safe BW Call";
                this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click);
                //  
                // backgroundWorker1 
                //  
                this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
                //  
                // Form1 
                //  
                this.ClientSize = new System.Drawing.Size(268, 96);
                this.Controls.Add(this.setTextBackgroundWorkerBtn);
                this.Controls.Add(this.setTextSafeBtn);
                this.Controls.Add(this.setTextUnsafeBtn);
                this.Controls.Add(this.textBox1);
                this.Name = "Form1";
                this.Text = "Form1";
                this.ResumeLayout(false);
                this.PerformLayout();
    
            }
    
            #endregion
    
    
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new Form1());
            }
    
        }
    }
    

    【讨论】:

    • 特立独行你说得对。这是我得到的例外:跨线程操作无效:控件'Form1'从创建它的线程以外的线程访问
    • @SharonGetter - 我在回答中为您添加了一些关于线程安全调用的说明。它有一个指向 MSDN 的链接以及他们的演示。
    • 我在计时器滴答事件中使用了它,现在它可以工作了。 frm1.Invoke(new MethodInvoker(delegate { frm1.CombindedStringFix(); }));
    • @SharonGetter - 是的......可以做到这一点 =DI 仍然建议查看该演示并尝试使用 BackgroundWorker 进行异步操作,因为它说这是首选方法。跨度>
    • 我会调查的。谢谢。
    【解决方案2】:

    我将尝试回答这个问题,但没有完整的上下文,这有点粗糙。

    1. 我注意到,在CombindedStringFix() 方法中,您有一个似乎是名为timer3 的计时器对象。此计时器的名称似乎与您在 SaveOldHtml 类中的计时器名称不匹配,并且我没有看到该名称公开的属性/字段。我想知道您是否有两个可能会混淆的计时器。

    我知道不回答这个问题是不好的形式,但在没有更多细节/更多来源的情况下,这是我能召集的最好的。更多上下文,我可以提供更多帮助!

    【讨论】:

    • CombindedStringFix 中 form1 中的计时器是 timer3,它在我的 form1 设计器中,它还有其他功能。它工作正常,与此问题无关。
    • 标记我如何上传或编辑我的问题与完整的上下文缺少什么?
    • @SharonGetter - 点击here,与点击帖子下方的编辑链接相同。
    • 我吃完午饭回来了,看来你们已经抓住了问题的实质。一路走好。
    猜你喜欢
    • 2016-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多