【问题标题】:timers in threading in .net.net线程中的计时器
【发布时间】:2010-08-16 18:47:34
【问题描述】:

我想要一个新线程中的计时器滴答/经过事件。好像我不能使用 Windows 计时器。但是,如果我使用 Timers.Timer,它会从线程池中为每个经过的事件创建工作线程。有没有办法让这些事件在同一个线程中发生?

更新:

感谢大家回答这个问题。

不过,我背后的意图可能听起来有点疯狂。我要你纠正我。当我试图实现这一点时,这是我的想法(作为新手)。我正在尝试每 2 秒执行一次任务。当我使用 Timers.Timer 时,它每 2 秒创建一个线程,我认为这是一种开销。

我的主线程和其他线程需要大量处理器时间来执行它们的任务。因此,如果我可以避免创建这些线程,那么每次计时器经过时,我都会为我的主线程和其他线程节省处理器的微秒时间来创建一个线程。

我进行了快速测试并比较了几个解决方案。每种情况下间隔 1000 毫秒。 100 滴答声。

解决方案 1:带有等待/睡眠的无限循环 {00:01:42.0068344}

解决方案 2:使用 Brian 的同步器 {00:01:42.4068573}

解决方案 3:Timers.Timer 原样 {00:01:42.4018571}

这应该告诉我 2.0068344、2.4068573、2.4018571 是在后台其他事情上浪费的时间,而不是 1000 毫秒的时间间隔为 100 个滴答声。这应该意味着当解决方案1满足您的需求时,它是性能方面的最佳解决方案?

这也应该意味着,虽然 Brian 的解决方案是同步到一个线程,但它实际上是在后台创建线程。

请确认或纠正我。

【问题讨论】:

  • 我很困惑。首先,您询问有关在新线程中运行代码的问题。然后你说你想在同一个线程上执行。是哪一个!?
  • 对不起.. 我不够清楚.. Timers.Timer 似乎为每个事件创建一个新线程.. 我正在寻找的是在新线程中运行代码.. 但是所有这些事件都应该发生在同一个新线程中,而不是为每个事件创建一个线程..
  • 也许你可以重新表述你的问题来解释你想要达到的目标。例如“我怎样才能定期做一些处理,但只允许一个线程在任何给定时刻处理这些数据?”
  • 您使用的是 WinForms 还是 WPF 并希望计时器使用 UI 线程?
  • @user:没有“C#.net”之类的东西,只有“C#”和“.NET”

标签: c# .net multithreading timer


【解决方案1】:

是的。您可以让System.Timers.Timer 在同一个线程中执行Elapsed 事件处理程序。为此,您需要将SynchronizingObject 属性设置为一个对象,该对象会将执行编组到您选择的线程上。但是,重要的是要意识到您不能只将执行注入到随机线程中。接收线程必须专门设计为允许封送方法调用的执行。

public static void Main(string[] args)
{
    var timer = new System.Timers.Timer();
    timer.Interval = 1000;
    timer.Elapsed += 
      (s, a) => 
      { 
        Console.WriteLine("{1} {0}", 
          DateTime.Now, Thread.CurrentThread.ManagedThreadId); 
      };
    timer.SynchronizingObject = new Synchronizer();
    timer.Start();
    Console.ReadLine();
}

这是Synchronizer 类的代码。此代码使用 .NET BCL 4.0 中的 BlockingCollection 类。如果您不使用 4.0,则可以将其替换为 Stephen Toub 的 BlockingQueue

public class Synchronizer : ISynchronizeInvoke
{
    private Thread m_Thread;
    private BlockingCollection<Message> m_Queue = new BlockingCollection<Message>();

    public Synchronizer()
    {
        m_Thread = new Thread(Run);
        m_Thread.IsBackground = true;
        m_Thread.Start();
    }

    private void Run()
    {
        while (true)
        {
            Message message = m_Queue.Take();
            message.Return = message.Method.DynamicInvoke(message.Args);
            message.Finished.Set();
        }
    }

    public IAsyncResult BeginInvoke(Delegate method, object[] args)
    {
        Message message = new Message();
        message.Method = method;
        message.Args = args;
        m_Queue.Add(message);
        return message;
    }

    public object EndInvoke(IAsyncResult result)
    {
        Message message = result as Message;
        if (message != null)
        {
            message.Finished.WaitOne();
            return message.Return;
        }
        throw new ArgumentException("result");
    }

    public object Invoke(Delegate method, object[] args)
    {
        Message message = new Message();
        message.Method = method;
        message.Args = args;
        m_Queue.Add(message);
        message.Finished.WaitOne();
        return message.Return;
    }

    public bool InvokeRequired
    {
        get { return Thread.CurrentThread != m_Thread; }
    }

    private class Message : IAsyncResult
    {
        public Delegate Method = null;
        public object[] Args = null;
        public object Return = null;
        public object State = null;
        public ManualResetEvent Finished = new ManualResetEvent(false);

        public object AsyncState
        {
            get { return State; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get { return Finished; }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return Finished.WaitOne(0); }
        }
    }
}

更新:

您需要了解System.Timers.Timer 的工作原理。它实际上在幕后使用System.Threading.Timer 并在ThreadPool 线程上执行事件处理程序。所以它根本没有真正创建任何线程。线程池中的相同线程被一遍又一遍地回收。没有线程创建开销。这就是线程池的全部意义所在。

现在,我在这里提供的解决方案仍然在幕后使用ThreadPool,但不是让工作项直接执行Elapsed 事件处理程序,而是执行Synchronizer.BeginInvoke 方法,该方法将事件处理程序编组到Synchronizer 创建的线程。

因此,我在这里提供的解决方案会慢一点也就不足为奇了。就个人而言,我会坚持解决方案#1,因为它更容易。在这一点上,我不会太担心性能。

【讨论】:

  • 很好,我编辑了写行以强调证明。随意回滚
  • 这种魔法是不存在的,只有当线程是UI线程时才能起作用。只有那个线程有一个同步提供者,由消息循环实现。其他线程将使用将使用线程池线程的默认提供程序。还不如使用 System.Windows.Forms.Timer
  • @Hans:我不确定我是否会将这个答案中的任何内容归类为魔术本身,但无论它被称为什么,它确实有效。 Synchronizer 类是同步提供程序,它创建了一个与 UI 线程分开的线程来执行此类操作。我不认为 OP 对System.Windows.Forms.Timer 感兴趣,因为它在 UI 线程上引发了不希望的事件;至少这是我阅读问题的方式。当然,尽管如此,我不会使用我在这里提供的解决方案来解决这个问题,但严格来说,我相信它回答了这个问题。
  • 我刚刚阅读了附加到问题的 cmets,并且 OP 确认了在与 UI 线程分开的线程(每次都相同)上发生System.Timers.Timer.Elapsed 事件的解决方案是意图。
  • Brian,您的两种解决方案都适合我。这就是我一直在寻找的。​​span>
【解决方案2】:

有没有办法让这些事件在同一个线程中发生?

不,这也没有多大意义。
事件不能仅仅“闯入”线程,线程必须合作。

您可以做的是创建一个重复循环并等待信号量的线程。然后使用任何类型的计时器来触发该信号量。

【讨论】:

    【解决方案3】:

    我认为提供另一个答案会有所帮助。原因是因为我的其他答案太复杂了。如果您想要做的只是确保在同一个线程上定期执行任务,那么实际上没有必要在 System.Timers.Timer 实例上设置 SynchronzingObject 属性。启动一个在无限循环中旋转的新线程要容易得多,它会执行您最初放置在 Elapsed 事件中的内容。

    public static void Main(string[] args)
    {
        var stop = new ManualResetEvent(false);
        var thread = new Thread(
            () =>
            {
                while (stop.WaitOne(YOUR_INTERVAL_GOES_HERE))
                {
                    // The code that was in the Elapsed event handler goes here instead.
                }
            });
        Console.WriteLine("Press ENTER to stop...");
        Console.ReadLine();
        stop.Set();
        thread.Join();
    }
    

    【讨论】:

      【解决方案4】:

      如果您希望在特定线程上执行定时事件,您至少有两个选择:

      • 启动您希望“计时器”在其上运行的线程,只需让它在计时器持续时间内休眠,然后执行您希望它执行的工作。
      • 让线程等待由计时器 (as Henk Holterman suggested) 发出的事件信号。这似乎是一种更复杂的方式来执行上述操作,但如果您希望线程能够通过计时器以外的方式被唤醒,则可能会有意义。

      【讨论】:

      • 工作和睡眠方法必须不断重新校准。
      【解决方案5】:

      我建议您创建一个线程来监视由计时器的经过事件处理程序提供的阻塞队列。那么事件处理程序使用哪个线程就无关紧要了,因为它所做的只是将一些东西放入队列中,因此该线程的持续时间将很短。

      有关良好的阻塞队列实现,请参阅 Marc Gravell 的回答:Creating a blocking Queue<T> in .NET?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多