C# - Thread.Join(毫秒) 和finally 块

发布于 2024-09-29 15:31:19 字数 5353 浏览 0 评论 0原文

我正在使用 .NET 2.0,如果线程超时,finally 块似乎不会被执行。例如,如果我看到消息“子线程超时...”,我将不会看到消息“终于块开始...”。这意味着数据库对象 (Oracle.DataAccess) 可能无法正确清理。有没有办法强制在子线程内进行清理,或者应该将清理移至主线程并将数据库对象传递给子线程?

   private void runThread(string strSP, object objThreadParameter)
    {
        try
        {
            bool blnThreadCompletedOK = true;

            Thread threadHelper = new Thread(getData);
            threadHelper.Start(objThreadParameter);

            // Wait for called thread.  
            blnThreadCompletedOK = threadHelper.Join(THREAD_TIMEOUT);
            if (blnThreadCompletedOK)
            {
                // Thread has completed and should have stopped running.
                // i.e. the thread has processed normally or an exception has been copied to the objExceptionThread object.
                if (objExceptionThread != null)
                {
                    throw objExceptionThread;
                }
            }
            else
            {
                System.Diagnostics.EventLog.WriteEntry("Main thread", "Child Thread Timed Out...", System.Diagnostics.EventLogEntryType.Warning);

                // Main thread has timed out waiting for the child thread.  Likely the child thread is still running.
                if (threadHelper.IsAlive)
                {
                    threadHelper.Abort();  // This will trigger the exception handling in the child thread and cause the finally
                                           // block to be executed.
                }
                throw (new Exception("The call to " + strSP + "() timed out as it exceeded " + (THREAD_TIMEOUT / 1000).ToString() + " seconds"));
            }
        }
        catch (Exception exc)
        {
            throw new PivotalApplicationException(exc.Message, exc, mrsysSystem);
        }
    }


    private void getData(object objThreadParameter)
    {
        OracleCommand oraCmd = null;
        OracleConnection oraConn = null;
        OracleDataReader dr = null;

        try
        {              
            // Initialization.
            int intMAX_RETRIES = 20;       // Maximum number of retries.
            int intRETRY_DROP_POOL = 5;    // At some point, if connections are still failing, try clearing the pool.

            // Other initialization stuff...

            // Now execute the SP.
            for (int i = 1; i <= intMAX_RETRIES; i++)
            {
                try
                {
                    try
                    {
                        // Setup Oracle connection and initialize Oracle command object.
                        getOracleConnection(out oraConn, connString);
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() setting up connection - " + exc.Message);
                    }

                    try
                    {
                        oraCmd = new OracleCommand(strSP, oraConn);
                        setupCommand (out oraCmd);
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() setting up parameters - " + exc.Message);
                    }

                    try
                    {
                        dr = oraCmd.ExecuteReader();
                        break; // Success, so, leave the for loop.
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() executing command.\n\n" + strParametersMsg + " \n\n" + exc.Message);
                    }
                }
                catch (Exception excInner)
                {

                    if (i >= intMAX_RETRIES)
                    {
                        throw new Exception(excInner.Message);
                    }
                    else
                    {
                        // Cleanup oraCmd, oraConn, oraDr...
                    }
                }
            }

            try
            {
                // Process results...
            }
            catch (Exception exc)
            {
                throw new Exception("Error in getData() processing results - " + exc.Message);
            }

            // Now set the variables that are shared between the Main thread and this thread...

        }
        catch (Exception exc)
        {
            logMessage(exc.Source + " " + exc.Message);
            objExceptionThread = exc;  // Initialize exception in Main Thread...
        }
        finally
        {
            System.Diagnostics.EventLog.WriteEntry("Child Thread", "Finally block started...", System.Diagnostics.EventLogEntryType.Warning);

            // With .NET 2.0 and later, the finally block should always be executed correctly for a Thread.Abort()
            if (!(dr == null))
            {
                dr.Dispose();
            }
            if (!(oraCmd == null))
            {
                oraCmd.Dispose();
            }
            if (!(oraConn == null))
            {
                oraConn.Close();
                oraConn.Dispose();
            }

            System.Diagnostics.EventLog.WriteEntry("Child Thread", "Finally block completed...", System.Diagnostics.EventLogEntryType.Warning);
        }
    }

I am using .NET 2.0 and the finally block does not seem to be getting executed if the Thread times out. For example, if I see the message "Child Thread Timed Out...", I will not see the message "Finally block started...". This means that the database objects (Oracle.DataAccess) may not be getting cleaned up properly. Is there a way to force the cleanup inside the child thread, or should the cleanup be moved to the main thread and pass in the database objects to the child thread?

   private void runThread(string strSP, object objThreadParameter)
    {
        try
        {
            bool blnThreadCompletedOK = true;

            Thread threadHelper = new Thread(getData);
            threadHelper.Start(objThreadParameter);

            // Wait for called thread.  
            blnThreadCompletedOK = threadHelper.Join(THREAD_TIMEOUT);
            if (blnThreadCompletedOK)
            {
                // Thread has completed and should have stopped running.
                // i.e. the thread has processed normally or an exception has been copied to the objExceptionThread object.
                if (objExceptionThread != null)
                {
                    throw objExceptionThread;
                }
            }
            else
            {
                System.Diagnostics.EventLog.WriteEntry("Main thread", "Child Thread Timed Out...", System.Diagnostics.EventLogEntryType.Warning);

                // Main thread has timed out waiting for the child thread.  Likely the child thread is still running.
                if (threadHelper.IsAlive)
                {
                    threadHelper.Abort();  // This will trigger the exception handling in the child thread and cause the finally
                                           // block to be executed.
                }
                throw (new Exception("The call to " + strSP + "() timed out as it exceeded " + (THREAD_TIMEOUT / 1000).ToString() + " seconds"));
            }
        }
        catch (Exception exc)
        {
            throw new PivotalApplicationException(exc.Message, exc, mrsysSystem);
        }
    }


    private void getData(object objThreadParameter)
    {
        OracleCommand oraCmd = null;
        OracleConnection oraConn = null;
        OracleDataReader dr = null;

        try
        {              
            // Initialization.
            int intMAX_RETRIES = 20;       // Maximum number of retries.
            int intRETRY_DROP_POOL = 5;    // At some point, if connections are still failing, try clearing the pool.

            // Other initialization stuff...

            // Now execute the SP.
            for (int i = 1; i <= intMAX_RETRIES; i++)
            {
                try
                {
                    try
                    {
                        // Setup Oracle connection and initialize Oracle command object.
                        getOracleConnection(out oraConn, connString);
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() setting up connection - " + exc.Message);
                    }

                    try
                    {
                        oraCmd = new OracleCommand(strSP, oraConn);
                        setupCommand (out oraCmd);
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() setting up parameters - " + exc.Message);
                    }

                    try
                    {
                        dr = oraCmd.ExecuteReader();
                        break; // Success, so, leave the for loop.
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() executing command.\n\n" + strParametersMsg + " \n\n" + exc.Message);
                    }
                }
                catch (Exception excInner)
                {

                    if (i >= intMAX_RETRIES)
                    {
                        throw new Exception(excInner.Message);
                    }
                    else
                    {
                        // Cleanup oraCmd, oraConn, oraDr...
                    }
                }
            }

            try
            {
                // Process results...
            }
            catch (Exception exc)
            {
                throw new Exception("Error in getData() processing results - " + exc.Message);
            }

            // Now set the variables that are shared between the Main thread and this thread...

        }
        catch (Exception exc)
        {
            logMessage(exc.Source + " " + exc.Message);
            objExceptionThread = exc;  // Initialize exception in Main Thread...
        }
        finally
        {
            System.Diagnostics.EventLog.WriteEntry("Child Thread", "Finally block started...", System.Diagnostics.EventLogEntryType.Warning);

            // With .NET 2.0 and later, the finally block should always be executed correctly for a Thread.Abort()
            if (!(dr == null))
            {
                dr.Dispose();
            }
            if (!(oraCmd == null))
            {
                oraCmd.Dispose();
            }
            if (!(oraConn == null))
            {
                oraConn.Close();
                oraConn.Dispose();
            }

            System.Diagnostics.EventLog.WriteEntry("Child Thread", "Finally block completed...", System.Diagnostics.EventLogEntryType.Warning);
        }
    }

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

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

发布评论

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

评论(3

盛夏尉蓝 2024-10-06 15:31:19

仅当该线程不再需要执行任何重要操作时,才应中止该线程。在其他情况下,我建议设置某种通知(即在线程上设置布尔属性),使线程正常关闭。话虽如此,根据文档finally 块应该在.NET 2.0 中执行:

当调用Abort 方法来销毁线程时,公共语言运行时会抛出ThreadAbortException。 ThreadAbortException 是一种可以捕获的特殊异常,但它会在 catch 块末尾自动再次引发。当引发此异常时,运行时会在结束线程之前执行所有的finally块。

我对所发生情况的最佳猜测是,主线程在线程上的finally块有机会执行之前退出。尝试在中止线程后放入 Thread.Sleep 来查看是否会改变行为。

编辑:
我使用 .NET 2.0 编写了一个简单的示例,它生成以下输出,显示 finally 块已执行。

活着并且正在踢

活着并且正在踢

活着并且正在踢

例外

终于

class ThreadTest
{
    public ThreadTest() { }

    public void test()
    {
        try
        {
            while (true)
            {
                Console.WriteLine("Alive and kicking");
                Thread.Sleep(2000);
            }
        }

        catch (Exception ex)
        {
            Console.WriteLine("Exception");
        }

        finally
        {
            Console.WriteLine("Finally");

        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        ThreadTest myThreadTest = new ThreadTest();
        Thread myThread = new Thread(new ThreadStart(myThreadTest.test));
        myThread.Start();
        Thread.Sleep(5000);
        bool status = myThread.Join(1000);
        if (myThread.IsAlive)
        {
            myThread.Abort();
        }
        Thread.Sleep(5000);
    }
}

You should only Abort a thread if that thread has nothing of importance to do any more. In other cases I'd recommend setting some kind of notification (i.e. setting a boolean property on the thread) that causes your thread to shut down gracefully. Having said that according to the documentation the finally block should be executed in .NET 2.0:

When a call is made to the Abort method to destroy a thread, the common language runtime throws a ThreadAbortException. ThreadAbortException is a special exception that can be caught, but it will automatically be raised again at the end of the catch block. When this exception is raised, the runtime executes all the finally blocks before ending the thread.

My best guess as to what's happening is that the main thread is exiting before the finally block on your thread has a chance to get executed. Try putting in a Thread.Sleep after aborting your thread to see if that changes the behavior.

Edit:
I wrote a simple example with .NET 2.0 which produces the following output that shows that the finally block is executed.

Alive and kicking

Alive and kicking

Alive and kicking

Exception

Finally

class ThreadTest
{
    public ThreadTest() { }

    public void test()
    {
        try
        {
            while (true)
            {
                Console.WriteLine("Alive and kicking");
                Thread.Sleep(2000);
            }
        }

        catch (Exception ex)
        {
            Console.WriteLine("Exception");
        }

        finally
        {
            Console.WriteLine("Finally");

        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        ThreadTest myThreadTest = new ThreadTest();
        Thread myThread = new Thread(new ThreadStart(myThreadTest.test));
        myThread.Start();
        Thread.Sleep(5000);
        bool status = myThread.Join(1000);
        if (myThread.IsAlive)
        {
            myThread.Abort();
        }
        Thread.Sleep(5000);
    }
}
随心而道 2024-10-06 15:31:19

仅当线程位于托管代码中时,您才能中断该线程。如果是本机代码,则运行时会安排在本机代码返回时抛出 ThreadAbortException。然后 finally 块将执行。在本机函数返回并恢复托管执行之前,finally 块不会运行。

如果您的问题是本机代码挂起,则finally块无法运行。

You can only interrupt a thread when it's in managed code. If it's in native code, then the runtime schedules the ThreadAbortException to be thrown when the native code returns. And finally blocks would execute after that. The finally block will not run until the native function returns and managed execution resumes.

If your problem is that the native code is hung, then the finally block cannot run.

咋地 2024-10-06 15:31:19

谢谢 - 问题似乎确实是时间问题。如果我重复检查 threadHelper.IsAlive 属性并保持主线程运行,则 finally 块会执行。所以,我认为代码挂在 dr = oraCmd.ExecuteReader(); 上
Thread.Join() 返回,尝试 Abort() - 但当时不能,然后主线程结束,因此子线程也被终止。我认为这确实留下了开放的联系。

ODP.NET 应该是一个托管数据提供程序 (http://wiki.oracle.com/page/Oracle+Data+Provider+for+.Net),它还有一个命令超时属性。

“CommandTimeout 指定在异常终止执行之前允许命令执行的秒数”。

我将进一步调查为什么 CommandTimeout 似乎不受尊重,如果仍然失败,我可能会尝试根据似乎拥有所有书籍的人结束应用程序域。

http://www.albahari.com/threading/part4.aspx#_Aborting_Threads

感谢您的帮助!

Thanks - The problem does seem to be one of timing. If I check the threadHelper.IsAlive property repeatedly and keep the main thread running, the finally block does execute. So, I think the code hangs on dr = oraCmd.ExecuteReader();
The Thread.Join() returns, attempts to Abort() - but can't at that time, and then the main thread ends so the child threads are also killed. I think that does leave an open connection though.

ODP.NET is supposed to be a managed data provider (http://wiki.oracle.com/page/Oracle+Data+Provider+for+.Net) and it also has a command timeout property.

"CommandTimeout Specifies the number of seconds the command is allowed to execute before terminating the execution with an exception".

I will investigate further why the CommandTimeout does not seem to be respected and if that still fails, I may try Ending Application Domains per the man who seems to have all the books.

http://www.albahari.com/threading/part4.aspx#_Aborting_Threads

Thanks for the help!

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