.net 中线程中的计时器

发布于 2024-09-14 18:02:14 字数 761 浏览 4 评论 0原文

我想要一个新线程内的计时器滴答/已过去事件。看来我不能使用 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 是除了 100 个刻度的 1000 毫秒时间间隔之外,在后台执行其他操作所浪费的时间。这应该意味着当解决方案1满足您的需求时,它是性能方面最好的解决方案?

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

请确认或纠正我。

I want a timer tick/elapsed event inside a new thread. It seems like I cannot use the Windows timer. But if I use the Timers.Timer, it creates worked thread from the threadpool for each elapsed event. Is there a way to make these events to occur in the same thread?

Update:

Thank you everybody for answering this question.

My intention behind the whole thing might sound a little crazy though. I want you to correct me. This is my thinking(as a novice) when I am trying to acieve this. I am trying perform a task every 2 seconds. When I use Timers.Timer, it creates a thread every 2 seconds which I am thinking it as an overhead.

My main thread and my other threads need a lot of processor's time to perform their tasks. So, if I can avoid creating these Threads, I am saving whatever microseconds of the processor to create a thread each time timer elapsed, for my main and other threads.

I made a quick tests and compared few solutions. 1000 msec interval in each case. 100 ticks.

Solution1: infinite loop with waits/sleep {00:01:42.0068344}

solution2: Using Brian's Synchronizer {00:01:42.4068573}

solution3: Timers.Timer as it is {00:01:42.4018571}

This should tell me that 2.0068344, 2.4068573, 2.4018571 are the times wasted for other things in the background other than the time interval of 1000 msec for 100 ticks. This should mean when solution1 meet your needs, it is the best solution performance-wise??

This should also mean that though Brian's solution is synchronized to one thread, it is in-fact creating threads in the background.

Please confirm it or correct me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

小…楫夜泊 2024-09-21 18:02:14

是的。您可以让 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 的 阻塞队列

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,因为它更容易。此时我不会太担心性能。

Yes. You can make the System.Timers.Timer execute the Elapsed event handler in the same thread. To do this you need to set the SynchronizingObject property to an object that will marshal the execution onto a thread of your choosing. However, it is important to realize that you cannot just inject execution onto a random thread. The receiving thread has to be specifically designed to allow marshaling the execution of method calls.

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();
}

And here is the code for the Synchronizer class. This code uses the BlockingCollection class from 4.0 of the .NET BCL. If you are not using 4.0 then you can replace it with Stephen Toub's 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); }
        }
    }
}

Update:

You need to understand how System.Timers.Timer works. It actually uses System.Threading.Timer behind the scenes and executes the event handler on a ThreadPool thread. So it is not really creating any threads at all. The same ones from the thread pool are getting recycled over and over again. There is no thread creation overhead. That is the whole point of a thread pool.

Now, the solution I provided here still uses the ThreadPool behind the scenes, but instead of having the work item execute the Elapsed event handler directly it is executing the Synchronizer.BeginInvoke method which marshals the event handler onto the thread that the Synchronizer created.

So it should be no surpise that the solution I provided here would be slower. Personally, I would stick to solution #1 as it is easier. I would not worry a whole lot about performance at this point.

夜未央樱花落 2024-09-21 18:02:14

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

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

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

Is there a way to make these events to occur in the same thread?

No, and that would not make much sense either.
An event cannot just 'break in' to a thread, the thread has to cooperate.

What you can do is to create a Thread that loops repeatedly and waits on a semaphore. Then use any kind of Timer to trigger that semaphore.

稚气少女 2024-09-21 18:02:14

我认为提供另一个答案会有好处。原因是因为我的另一个答案让事情变得太复杂了。如果您只想确保在同一个实例上定期执行任务,则确实没有必要在 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();
}

I thought it would beneficial to provide another answer. The reason is because my other answer complicates things too much. It is really unnecessary to set the SynchronzingObject property on a System.Timers.Timer instance if all you are wanting to do is make sure that a task is executed periodically on the same thread. It is much easier to start a new thread that spins around in an infinite loop executing what you would have originally placed in the Elapsed event.

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();
}
秋心╮凉 2024-09-21 18:02:14

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

  • 启动您希望“计时器”运行的线程,并简单地让它在计时器持续时间内休眠,然后执行您想要的工作它要做。
  • 让线程等待计时器发出的事件信号(as Henk Holterman建议)。这似乎是执行上述操作的更复杂的方法,但如果您希望线程能够通过计时器以外的方式唤醒,则可能有意义。

If you want a timed event to execute on a particular thread, you have at least a couple options:

  • spin up the thread you want the 'timer' to run on and simply have it sleep for the timer duration, then do the work you want it to do.
  • have the thread wait on a an event signaled by a timer (as Henk Holterman suggested). This seems like a more complicated way to do the above, but might make sense if you want the thread to be able to be awakened by means other than the timer.
停顿的约定 2024-09-21 18:02:14

我建议您创建一个线程来监视由计时器的已用事件处理程序提供的阻塞队列。那么事件处理程序使用哪个线程并不重要,并且由于它所做的只是将某些内容放入队列中,因此该线程的持续时间将很短。

有关良好的阻塞队列实现,请参阅 Marc Gravell 的回答:创建阻塞队列在.NET 中?

I recommend that you create a thread that watches a blocking queue that is fed by your timer's elapsed event handler. Then it won't matter which thread the event handler uses, and since all it does is drop something into the queue, that thread's duration will be brief.

For a good blocking queue implementation, see Marc Gravell's answer: Creating a blocking Queue<T> in .NET?.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文