如何在C#中使用定时器

18

我正在使用system.Timers.Timer来创建一个定时器。

public System.Timers.Timer timer = new System.Timers.Timer(200);
private void btnAutoSend_Click(object sender, EventArgs e)
{
    timer.Enabled = true;
    timer.Elapsed += new System.Timers.ElapsedEventHandler(send);
    timer.AutoReset = true;
}

public void send(object source, System.Timers.ElapsedEventArgs e)
{
    this.rtbMsg.AppendText("psyche-->" + receiver + ": hello\n");
}

send函数中的接收者是一个参数,我需要在使用该函数时设置,但是当我添加一个参数到send函数中时,比如:

public void send(object source, System.Timers.ElapsedEventArgs e,string receiver)

然后它会抛出一个错误。在我查看MSDN后得知,ElapsedEventArgs仅适用于不生成数据的这些函数。

我应该如何解决这个问题?我的程序不是Windows.Form,因此无法使用System.Windows.Forms.Timer


你可以使用 System.Windows.Forms.Timer,只需添加对该库的引用。 - annonymously
如何将发送方参数传递给System.Timers.Timer?请参考以下链接:https://dev59.com/91jUa4cB1Zd3GeqPQEw9 - Damith
不应该在任何UI组件中使用System.Windows.Timer(例如,rtbMsg.AppendText可能会访问某种Windows控件),System.Timer.Elapsed在非UI线程上调用,大多数情况下会导致异常。如果您不是WinForms而是WPF,则需要使用DispatchTimer。如果您能够澄清您收到的错误,有人可能能够提供更精确的建议。 - Peter Ritchie
3个回答

22

你不能向事件处理程序回调函数传递额外的参数,因为调用它的不是你 -- 而是计时器;这就是整个问题的重点 ;-)

但是,你可以通过闭包轻松实现相同的效果:

private void btnAutoSend_Click(object sender, EventArgs e)
{
    timer.Elapsed += (timerSender, timerEvent) => send(timerSender, timerEvent, receiver);
    timer.AutoReset = true;
    timer.Enabled = true;
}

public void send(object source, System.Timers.ElapsedEventArgs e, string receiver)
{
    this.rtbMsg.AppendText("psyche-->" + receiver + ": hello\n");
}

现在,经过处理后的 Elapsed 处理程序是 (timerSender, timerEvent) => lambda 表达式,它封闭了 receiver 变量,并在每次触发 lambda 时手动调用 send 函数并传入额外的参数。

在您的特定情况下,您根本不需要发送者或参数,因此无需将它们转发。代码变为:

private void btnAutoSend_Click(object sender, EventArgs e)
{
    timer.Elapsed += (s_, e_) => OnTimerElapsed(receiver);
    timer.AutoReset = true;
    timer.Enabled = true;
}

private void OnTimerElapsed(string receiver)
{
    this.rtbMsg.AppendText("psyche-->" + receiver + ": hello\n");
}
如果你想知道这些的开销,那么它非常小。Lambda只是语法糖,在幕后是普通函数(对于事件部分会有一些自动委托包装)。闭包使用编译器生成的类来实现,但除非你真正有大量闭包,否则你不会注意到任何代码臃肿。
如评论中所指出的,您似乎正在OnTimerElapsed代码中访问UI元素--由于您没有使用Windows窗体计时器,在执行事件时代码将在计时器运行的任何线程上运行,并且在Windows中的UI控件必须仅从创建它们的线程访问。
您可以通过 SynchronizingObject属性手动修复它,但最好让计时器为您将事件传递到正确的线程:
private void btnAutoSend_Click(object sender, EventArgs e)
{
    timer.SynchronizingObject = this;    // Assumes `this` implements ISynchronizeInvoke
    timer.Elapsed += (s_, e_) => OnTimerElapsed(receiver);
    timer.AutoReset = true;
    timer.Enabled = true;
}

最后,在另一条评论的提示下,这里提供另一种方法来存储对闭包的引用,以便稍后可以取消订阅事件:

private void btnAutoSend_Click(object sender, EventArgs e)
{
    timer.SynchronizingObject = this;    // Assumes `this` implements ISynchronizeInvoke
    ElapsedEventHandler onElapsed;
    onElapsed = (s_, e_) => {
        timer.Elapsed -= onElapsed;    // Clean up after firing
        OnTimerElapsed(receiver);
    };
    timer.Elapsed += onElapsed;
    timer.AutoReset = true;
    timer.Enabled = true;
}

为什么在设置自动重置和回调之前启动计时器?timer.Enabled=true 应该是你做的最后一件事。 - nbrooks
@nbrooks: 说得好,我只是在复制原帖的代码。我会进行编辑。 - Cameron
@nbrooks 这和 timer.Start() 一样吗? - Damith
1
@damith 是的,timer.Start() 只是将 enabled 设置为 true。 - nbrooks
OP说这不是一个WinForm应用程序,没有this.Invoke。最好只使用与涉及的UI框架相适应的定时器。 - Peter Ritchie
显示剩余2条评论

3

你不能像那样向事件处理程序传递额外的参数。

将值存储在对象级变量中,以便可以在事件处理程序中访问它。

private string receiver;

public System.Timers.Timer timer = new System.Timers.Timer(200);
private void btnAutoSend_Click(object sender, EventArgs e)
{
    timer.Enabled = true;
    receiver = 'your val';
    timer.Elapsed += new System.Timers.ElapsedEventHandler(send);
    timer.AutoReset = true;
}

public void send(object source, System.Timers.ElapsedEventArgs e)
{
    this.rtbMsg.AppendText("psyche-->" + receiver + ": hello\n");
}

2
public partial class Form2 : Form
{
    Timer timer = new Timer();
    public Form2()
    {
        InitializeComponent();
        timer.Tick += new EventHandler(timer_Tick); // Every time timer ticks, timer_Tick will be called
        timer.Interval = (10) * (1000);             // Timer will tick every 10 seconds
        timer.Start();                              // Start the timer
    }
    void timer_Tick(object sender, EventArgs e)
    {
        //MessageBox.Show("Tick");                    // Alert the user
        var time = DateTime.Now;
        label1.Text = $"{time.Hour} : {time.Minute} : {time.Seconds} : {time.Milliseconds}";
    }
    private void Form2_Load(object sender, EventArgs e)
    {

    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接