C#终止线程

4

在我的应用程序中,我有一个连续运行的线程。通过使用Thread.Sleep(),该函数每10分钟执行一次。

我需要能够在用户单击按钮时杀死此线程。我知道Thread.Abort()不可靠。我可以使用变量来停止线程,但由于它正在休眠,可能需要另外10分钟才能使线程自动停止。

有什么好的建议吗?


你可以随时从睡眠-等待-加入状态中Interrupt()一个线程,并让它读取一些变量,告诉它中止... - Cipi
5个回答

9
为什么不使用定时器每十分钟调度任务。这将在线程池线程上运行代码,因此您无需自己管理。有关更多详细信息,请参见System.Threading.Timer类。

有趣。你能给我更多细节吗?我对这个还有点陌生。 - Andy Hin
你仍然需要解决取消周期性操作的问题。但现在是禁用计时器而不是结束线程。 - Ben Voigt
我可以通过禁用定时器来简单取消操作,对吧? - Andy Hin
1
@whydna 这种方法还突出了您原始代码中一个微妙的错误:它不会每10分钟执行一次,而是每(10分钟+执行时间)执行一次。根据您正在做什么,这可能很重要,也可能不重要。 - CurtainDog

4

以下是使用计时器执行工作的示例,正如Brian所建议的那样。根据需要使用开始/停止。在完成对象(Program)后清理它时,请确保调用Dispose。

请注意,当您调用Stop时,它将防止计时器再次触发,但是您仍然可能有一个正在执行timer_Elapsed处理程序的工作线程,即停止计时器不会停止任何当前正在执行的工作线程。

using System;
using System.Timers;

namespace TimerApp
{
    class Program : IDisposable
    {
        private Timer timer;

        public Program()
        {
            this.timer = new Timer();
            this.timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            this.timer.AutoReset = true;
            this.timer.Interval = TimeSpan.FromMinutes(10).TotalMilliseconds;
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            // TODO...your periodic processing, executed in a worker thread.
        }

        static void Main(string[] args)
        {
            // TODO...your app logic.
        }

        public void Start()
        {
            this.timer.Start();
        }

        public void Stop()
        {
            this.timer.Stop();
        }

        public void Dispose()
        {
            this.timer.Dispose();
        }
    }
}

4

不要使用Thread.Sleep,而是使用System.Threading.ManualResetEventWaitOne方法和Thread.Sleep一样有一个超时时间,除非事件先被触发,否则您的线程将在该间隔内休眠,并且返回值会告诉您是否经过了该间隔还是事件已设置。


3
建立在Ben的答案之上,这是一个可以帮助你的模式...
using System.Threading;

public class MyWorker {
        private ManualResetEvent mResetEvent = new ManualResetEvent(false);
        private volatile bool mIsAlive;
        private const int mTimeout = 6000000;

        public void Start()
        {
            if (mIsAlive == false)
            {
                mIsAlive = true;
                Thread thread = new Thread(new ThreadStart(RunThread));
                thread.Start();
            }
        }

        public void Stop()
        {
            mIsAlive = false;
            mResetEvent.Set();
        }

        public void RunThread()
        {
            while(mIsAlive)
            {
                //Reset the event -we may be restarting the thread.
                mResetEvent.Reset();

                DoWork();

                //The thread will block on this until either the timeout
                //expires or the reset event is signaled.
                if (mResetEvent.WaitOne(mTimeout))
                {
                    mIsAlive = false; // Exit the loop.
                }
            }
        }

        public void DoWork()
        {
            //...
        } }

这个示例存在一个小问题。它等待超时时间以及DoWork()所花费的时间。如果工作时间超过瞬间,计时将会出错。应该从(上次工作开始时间 + mTimeout)中减去当前时间;如果结果为负数,则根本不需要等待,直接循环回去(任务执行时间超过了mTimeout)。 - devstuff
没错,但是由于OP使用了sleep而且没有提到硬要求周期,我就没同步周期。这样可以保留定时行为,同时允许外部线程按需终止循环。当然,有时候两种方法都适用,更别说还有一种情况,如果当前周期的剩余持续时间低于某个阈值,那么可能会跳过下一个周期。编辑:如果DoWork()需要花费接近周期的时间,他将不得不在其他地方评估mIsAlive以提前终止它。这段代码只是按需打破“睡眠”。 - Rob Cooke
此外,您应该在某个地方实际创建事件。对于类而言,仅声明变量是不足以创建实例的。 - Ben Voigt

2
一种可能的解决方案是不让它睡眠十分钟。让它睡眠10秒,然后只在每60次唤醒时进行工作。这样,停止前你只有10秒的延迟。
引用: 旁注:这不一定是最好的解决方案,但可能是最快实现的解决方案。对于所有可能性,在选择正确的解决方案时应进行成本/效益分析。
如果10秒仍然太长,可以进一步减少,但请记住,过度减少会导致可能的性能影响。
你说得对,你不应该从外部杀死线程,因为如果它们在kill时持有某些资源的锁定且该资源未被释放,则很可能会造成灾难。线程应始终负责其自己的资源,包括生命周期。

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