【问题标题】:How to tell if a thread has exited successfully?如何判断线程是否成功退出?
【发布时间】:2009-06-16 14:47:27
【问题描述】:

根据MSDN Documentation for ThreadState,可以通过以下两种方式之一进入停止状态:线程退出,或线程被中止。

是否有某种机制可以通过正常退出来判断线程是否已进入 Stopped 状态?谢谢!

【问题讨论】:

    标签: c# multithreading thread-state


    【解决方案1】:

    线程可以通过多种方式达到 Stopped 状态:

    • 它的main方法可以正常退出。
    • 线程上未捕获的异常可以终止它。
    • 另一个线程可以调用 Thread.Abort(),这将导致在该线程上引发 ThreadAbortException。

    我不知道您是否希望区分所有三种状态,但如果您真正感兴趣的是线程是否成功完成,我建议使用某种共享数据结构(同步字典将工作)线程的主循环在它终止时更新。您可以使用 ThreadName 属性作为此共享字典中的键。其他对终止状态感兴趣的线程可以从这个字典中读取以确定线程的最终状态。

    在更多地查看MSDN documentation 之后,您应该能够使用ThreadState 属性来区分外部中止的线程。当线程响应 Abort() 调用时,应将其设置为 ThreadState.Aborted。但是,除非您可以控制正在运行的线程代码,否则我认为您无法区分刚刚退出其 main 方法的线程与以异常终止的线程。

    但请记住,如果您控制启动线程的代码,您始终可以替换您自己的方法,该方法在内部调用运行主线程逻辑的代码并注入异常检测(如我如上所述)那里。

    【讨论】:

      【解决方案2】:

      你想监控你的代码的线程吗?如果是这样,您可以将整个事情包装在一个类中,并在完成时引发事件或使用 WaitHandle(我将使用 ManualResetEvent)来表示完成。 -- 完全封装后台逻辑。您也可以使用这种封装来捕获异常,然后引发事件。

      类似这样的:

      using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace BackgroundWorker { public class BackgroundWorker { /// /// Raised when the task completes (you could enhance this event to return state in the event args) /// public event EventHandler TaskCompleted; /// /// Raised if an unhandled exception is thrown by the background worker /// public event EventHandler BackgroundError; private ThreadStart BackgroundTask; private readonly ManualResetEvent WaitEvent = new ManualResetEvent(false); /// /// ThreadStart is the delegate that you want to run on your background thread. /// /// public BackgroundWorker(ThreadStart backgroundTask) { this.BackgroundTask = backgroundTask; } private Thread BackgroundThread; /// /// Starts the background task /// public void Start() { this.BackgroundThread = new Thread(this.ThreadTask); this.BackgroundThread.Start(); } private void ThreadTask() { // the task that actually runs on the thread try { this.BackgroundTask(); // completed only fires on successful completion this.OnTaskCompleted(); } catch (Exception e) { this.OnError(e); } finally { // signal thread exit (unblock the wait method) this.WaitEvent.Set(); } } private void OnTaskCompleted() { if (this.TaskCompleted != null) this.TaskCompleted(this, EventArgs.Empty); } private void OnError(Exception e) { if (this.BackgroundError != null) this.BackgroundError(this, new BackgroundWorkerErrorEventArgs(e)); } /// /// Blocks until the task either completes or errrors out /// returns false if the wait timed out. /// /// Timeout in milliseconds, -1 for infinite /// public bool Wait(int timeout) { return this.WaitEvent.WaitOne(timeout); } } public class BackgroundWorkerErrorEventArgs : System.EventArgs { public BackgroundWorkerErrorEventArgs(Exception error) { this.Error = error; } public Exception Error; } }

      注意:您需要一些代码来防止 Start 被调用两次,并且您可能希望添加一个 state 属性来将状态传递给您的线程。

      这是一个控制台应用程序,演示了此类的使用:

      using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace BackgroundWorker { class Program { static void Main(string[] args) { Console.WriteLine("Test 1"); BackgroundWorker worker = new BackgroundWorker(BackgroundWork); worker.TaskCompleted += new EventHandler(worker_TaskCompleted); worker.BackgroundError += new EventHandler(worker_BackgroundError); worker.Start(); worker.Wait(-1); Console.WriteLine("Test 2"); Console.WriteLine(); // error case worker = new BackgroundWorker(BackgroundWorkWithError); worker.TaskCompleted += new EventHandler(worker_TaskCompleted); worker.BackgroundError += new EventHandler(worker_BackgroundError); worker.Start(); worker.Wait(-1); Console.ReadLine(); } static void worker_BackgroundError(object sender, BackgroundWorkerErrorEventArgs e) { Console.WriteLine("Exception: " + e.Error.Message); } private static void BackgroundWorkWithError() { throw new Exception("Foo"); } static void worker_TaskCompleted(object sender, EventArgs e) { Console.WriteLine("Completed"); } private static void BackgroundWork() { Console.WriteLine("Hello!"); } } }

      【讨论】:

        【解决方案3】:

        您可能想查看 BackgroundWorker 类。它有一个用于线程完成时的通用事件处理程序。在那里,您可以检查线程是否由于错误、被取消或成功完成而完成。

        【讨论】:

        • 有趣的是,您应该提到...我实际上正在实现 BackgroundWorker 类的不同风格 - 您可以在其中取消长时间运行的操作。不过感谢您的建议!
        • 我的理解是,运行 BackgroundWorker 实例的线程实际上并没有在工作者完成时终止。您可以确定工作人员何时完成工作,但这与线程终止的时间不同。现在,鉴于这个问题,这可能是一种确定工作任务状态的可行技术......但值得指出的区别。
        • 您可以使用默认后台工作线程取消长时间运行的进程。只需调用 CancelAsync。在工作线程中,您必须查找 CancellationPending 标志
        • 这在我感兴趣的情况下不起作用 - 后台线程完成可能长时间运行的网络操作(即检索 SQL 服务器列表)。据我了解, CancelAsync 为您在处理代码中手动处理设置了一个标志,如果您的处理代码是单个阻塞调用,这将无济于事。相信我,这是我看到的第一件事。 :) 谢谢!
        【解决方案4】:

        假设主线程需要等待工作线程成功完成,我一般使用ManualResetEvent。或者,对于多个工作线程,来自 Parallels Extensions 的新 CountDownEvent,例如 so

        【讨论】:

          【解决方案5】:

          我使用CancellationTokenSource 要求线程优雅地退出。

          然后我使用 var exitedProperly = _thread.Join(TimeSpan.FromSeconds(10); 等待线程退出。

          如果exitedProperly==false,我将错误推送到日志中。

          我主要在Dispose() 函数中使用此模式,并且我正在尝试清理我创建的所有线程。

          【讨论】:

            【解决方案6】:

            只能通过调用 Thread.Abort() 来中止线程,这会导致 ThreadAbortException,因此通过异常处理,您应该能够确定正常退出与中止退出。

            【讨论】:

            • 据我所知,ThreadAbortException 仅在实际中止的线程中引发,而不是在调用代码中引发。因此,我无法在调用代码中捕获该异常。
            • @Pwninstein 没错。我假设您可以控制线程代码。如果您只能访问调用代码,那么我的回答对您没有任何用处。
            猜你喜欢
            • 2012-01-24
            • 1970-01-01
            • 1970-01-01
            • 2013-01-14
            • 2010-12-12
            • 1970-01-01
            • 1970-01-01
            • 2023-04-09
            • 2013-09-27
            相关资源
            最近更新 更多