【问题标题】:Idea for Timer with thread C# Window Form带有线程 C# 窗口窗体的计时器的想法
【发布时间】:2015-10-09 06:42:14
【问题描述】:

我的项目有一个 MainForm,我显示 F_Insert 并为 MainForm 设置 MdiParent

F_Insert f = new F_Insert();
f.MdiParent = this;  
f.Show();

在 F_Insert 中,我像这样放置了一个带有 CLick 事件的按钮

 private void btn_Add_Click(object sender, EventArgs e)
 {
       //Insert data to SQL
 }

此外,我想每隔 5 秒自动上传从 F_Insert 插入的数据

我使用 System.Timer.Timer 并将其设置为 MainForm_Load 中的 Thread

Thread t1 = new Thread(new ThreadStart(Timerss)); //In MainFormLoad event
t1.Start(); 

public void Timerss()
    {
        System.Timers.Timer timer = new System.Timers.Timer(5000);
        timer.Elapsed += Timer_Insert_Tick;
        timer.AutoReset = true;
        timer.Start();
    }

private static void Timer_Insert_Tick(object sender, System.Timers.ElapsedEventArgs e)
    {
      //code auto upload data to server here
      //Data get from Sql Local to upload SQL in Server
    }

问题是它工作得不好。我觉得当我从 F_Insert 插入数据时,数据会受到我在 MainForm 加载中启动的 Timerss 线程的影响。

向您展示我的问题的简单方法:当我拆分两个工作时(插入 并上传)到 2 个不同的工作中,它工作得很好,这意味着我 插入数据完成,然后,我上传数据,它会工作得很好。 但是当我同时插入数据和定时器自动上传的数据时, 我看到一些错误:连接 sql 关闭或打开错误,没有数据获取 从 F_Insert,有时会得到重复数据(旧数据)

请就这个问题给我一些建议。对不起,但我是线程的新手。谢谢!!!

【问题讨论】:

  • 什么不起作用?你怎么知道不是?什么错误?等等……
  • 我在这里看到的一个问题(我不清楚您的实际问题是什么)是对计时器的唯一引用是在局部变量中。我相信这意味着它可以并且将会被垃圾收集。
  • 嗨@Enigmativity,感谢收看!向您展示我的问题的简单方法:当我将两个工作(插入和上传)分成两个不同的工作时,它工作得很好,这意味着我插入数据完成,然后我上传数据,它会工作得很好。但是当我同时插入数据和数据自动上传时,我看到一些错误:连接sql关闭或打开错误,没有从F_Insert获取数据,有时它会得到重复数据(旧数据)......
  • 嗨@Damien_The_Unbeliever,我对你的awser很感兴趣,但我不明白你的awser。请告诉我更多关于它的信息!谢谢你!
  • 如果我在 FCL 团队中,我会将 [Obsolete("NEVER CREATE THREADS DIRECTLY UNLESS YOU KNOW EXACTLY WHAT ARE YOU DOING")] 放在 Thread 构造函数上。不使用 TPL 或 async/await 的原因是什么?你在 .NET 3.5 上吗?

标签: c# multithreading timer thread-safety


【解决方案1】:

这取决于你试图做什么,这段代码应该被修改,但我希望它会给你一个开始工作的起点。

首先让我们创建静态字段:

static volatile bool isDataChanged;

关键字volatile 使这个布尔值线程安全,这意味着当多线程环境中的任何线程访问该字段时,该字段始终保持最新(因此是正确的)值。

我们需要这个字段来保存 bool 值,稍后用于检查数据是否被修改。

假设数据在 click 事件处理程序中被修改,我们应该将此标志设置为 true:

 private void btn_Add_Click(object sender, EventArgs e)
 {
       // Data is modified in UI thread

       isDataChanged = true;
 }

然后让我们假设在 Timer tick 事件中我们应该将最新数据上传到数据库(数据位于 UI 线程中,并且可能在两个 tick 事件之间的时间跨度上发生变化)。

首先,我们检查我们的数据是否有任何变化,如果没有,我们就退出该方法。如果更改完成,我们需要将它们上传到 DB,为此我们必须处理 Timer 线程中的数据很可能与 UI 线程中的数据不同的事实。

让我们创建一个局部变量来保存我们从 UI 线程获取的正确数据,并使用 this.Invoke() 在 UI 线程上调用 Func<object> 委托。附加到委托的方法将从 UI 线程中检索到的正确数据实例返回为object。我们将其显式转换为我们的数据类型(通常是集合类型之一,如List<T>Dictionary<T1, T2>),并使用此数据将其上传到数据库。

之后,因为我们在 DB 中的数据是正确的,我们将标志 isDataChanged 更改为 false

private void Timer_Insert_Tick(object sender, System.Timers.ElapsedEventArgs e)
{
   if(!isDataChanged) return;

   // A very important line. It gets data from UI thread before uploading it
   // Change DataType with your data Type and dataToUpload with data instance

   DataType data = (DataType)this.Invoke(new Func<object>(() => dataToUpload));

   //use data to upload your data to server

   isDataChanged = false;
}

附: 此外,最好将 Timer 的引用放在外部范围内(以便可以从表单内的任何位置访问它)

public partial class MyForm : Form
{
   ...
   System.Timers.Timer timer; 

   public void Timerss()
   {
      timer = new System.Timers.Timer(5000); 
   }

   ...
}

【讨论】:

  • 谢谢,看来是我的问题。我会试试你的方法。非常感谢
  • @XCode2015 它应该可以帮助您处理多线程,但如果数据库、sql 查询或数据处理出现问题,那就完全不同了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-17
  • 1970-01-01
  • 1970-01-01
  • 2012-06-12
  • 1970-01-01
  • 2015-05-08
  • 1970-01-01
相关资源
最近更新 更多