成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊一聊 C# 前臺線程如何阻塞程序退出

開發 前端
這篇文章起源于我的 C#內功修煉訓練營里的一位朋友提的問題:后臺線程的內部是如何運轉的 ? ,猶記得C# Via CLR這本書中 Jeffery 就聊到了他曾經給別人解決一個程序無法退出的bug,最后發現是有一個 Backgrond=false 的線程導致的。

一、背景

1. 講故事

這篇文章起源于我的 C#內功修煉訓練營里的一位朋友提的問題:后臺線程的內部是如何運轉的 ? ,猶記得C# Via CLR這本書中 Jeffery 就聊到了他曾經給別人解決一個程序無法退出的bug,最后發現是有一個 Backgrond=false 的線程導致的。恰巧在我分析的350+dump中,也還真遇到了。有了這些鋪墊,我覺得有必要簡單的聊一聊。

二、后臺線程的底層邏輯

1. 測試代碼

為了方便講解,先上一段代碼,參考如下:

static void Main(string[] args)
    {
        var thread = new Thread(() =>
        {
            while (true)
            {
                Console.WriteLine(DateTime.Now);
            }
        });

        thread.IsBackground = false;
        thread.Start();
    }

圖片圖片

按照我們樸素的想法,主線程退出,程序自然就terminal,但這個程序并沒有退出?原因就在于設置了 thread.IsBackground = false; 導致的,當然要想程序正常退出改為 ``thread.IsBackground = true;` 即可,接下來我們洞察下 IsBackground 有何魔力導致程序無法退出。

2. 程序為什么無法退出

要想知道這個答案,可以用 windbg 附加一下看看主線程此時正在做什么? 參考如下:

0:000> k
 # Child-SP          RetAddr               Call Site
00 0000003f`7d59e498 00007ffd`cd8d0590     ntdll!NtWaitForMultipleObjects+0x14
01 0000003f`7d59e4a0 00007ffd`8f842dd4     KERNELBASE!WaitForMultipleObjectsEx+0xf0
02 (Inline Function) --------`--------     coreclr!Thread::DoAppropriateAptStateWait+0x4a [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 3333] 
03 0000003f`7d59e790 00007ffd`8f842c25     coreclr!Thread::DoAppropriateWaitWorker+0x170 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 3467] 
04 0000003f`7d59e850 00007ffd`8f99498e     coreclr!Thread::DoAppropriateWait+0x85 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 3182] 
05 (Inline Function) --------`--------     coreclr!CLREventBase::WaitEx+0x26 [D:\a\_work\1\s\src\coreclr\vm\synch.cpp @ 459] 
06 (Inline Function) --------`--------     coreclr!CLREventBase::Wait+0x26 [D:\a\_work\1\s\src\coreclr\vm\synch.cpp @ 412] 
07 0000003f`7d59e8d0 00007ffd`8f94c185     coreclr!CLREventWaitWithTry+0x9a [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5676] 
08 0000003f`7d59e980 00007ffd`8f8a062b     coreclr!ThreadStore::WaitForOtherThreads+0xabafd [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5715] 
09 0000003f`7d59e9b0 00007ffd`8f83eaad     coreclr!RunMainPost+0x5f [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1407] 
0a 0000003f`7d59e9f0 00007ffd`8f83e0e7     coreclr!Assembly::ExecuteMainMethod+0x1f5 [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1524] 
0b 0000003f`7d59ecc0 00007ffd`8f889778     coreclr!CorHost2::ExecuteAssembly+0x267 [D:\a\_work\1\s\src\coreclr\vm\corhost.cpp @ 349] 
...

從卦中數據可以看到,主線程正在調用 ThreadStore::WaitForOtherThreads 方法,貌似是在等待其他線程完成,那具體做了什么呢?這個需要在 coreclr 上尋找答案,刪減后的代碼如下:

void ThreadStore::WaitForOtherThreads()
    {
        if (!OtherThreadsComplete())
        {
            TSLockHolder.Release();

            pCurThread->SetThreadState(Thread::TS_ReportDead);

            DWORD ret = WAIT_OBJECT_0;
            while (CLREventWaitWithTry(&m_TerminationEvent, INFINITE, TRUE, &ret))
            {
            }
        }
    }

    BOOL OtherThreadsComplete()
    {
        return (m_ThreadCount - m_UnstartedThreadCount - m_DeadThreadCount
                - Thread::m_ActiveDetachCount + m_PendingThreadCount
                == m_BackgroundThreadCount);
    }

從卦中看邏輯還是非常簡單的,就是因為 m_ThreadCount - m_UnstartedThreadCount - m_DeadThreadCount- Thread::m_ActiveDetachCount + m_PendingThreadCount 減完之后和 m_BackgroundThreadCount 對不上,最后在 m_TerminationEvent 事件上等待喚醒。

這里稍微提一下,這幾個值可以通過 !t 顯示出來,參考如下:

圖片圖片

還有一個 Thread::m_ActiveDetachCount 計數值,這個值統計的是那種被coreclr從 ThreadStore 中移除尚未被 delete 的線程對象。結合 !t 的輸出,很顯然 OtherThreadsComplete() 為 3=2 顯然返回 false。因為有 1 個 background 的存在。

3. IsBackground=true 能破局嗎

癥結我們也找到了,只要m_TerminationEvent事件能夠被喚醒,鏈路就會被再次打通,讓程序安全退出。接下來我們研究下 IsBackground=true 在底層會做什么?簡化后的C++代碼如下:

void Thread::SetBackground(BOOL isBack)
    {
        if (isBack)
        {
            if (!IsBackground())
            {
                SetThreadState(TS_Background);

                if (!IsUnstarted())
                    ThreadStore::s_pThreadStore->m_BackgroundThreadCount++;

                ThreadStore::CheckForEEShutdown();
            }
        }
    }

    void ThreadStore::CheckForEEShutdown()
    {
        if (g_fWeControlLifetime &&
            s_pThreadStore->OtherThreadsComplete())
        {
            BOOL bRet;
            bRet = s_pThreadStore->m_TerminationEvent.Set();
            _ASSERTE(bRet);
        }
    }

哈哈,卦中的化煞方法真的妙不可言,做了如下兩個步驟:

  • 做了 m_BackgroundThreadCount++,這樣 OtherThreadsComplete() 的值就對上了。
  • 使用 m_TerminationEvent.Set 做了事件喚醒,這樣主線程就可以從 WaitForOtherThreads() 方法中逃出生天。

如果有些朋友沒搞明白,我再畫一張簡圖吧:

圖片圖片

4. 判斷線程的前后狀態

這是最后一個要聊的話題,要想知道線程的前后狀態,這個需要在 coreclr 源碼中尋找答案,參考代碼如下:

void SetThreadState(ThreadState ts)
    {
        InterlockedOr((LONG*)&m_State, ts);
    }

    enum ThreadState
    {
        TS_Background = 0x00000200,    // Thread is a background thread
    }

從代碼中可以看到,只要判斷 ThreadState 中有沒有 0x200 的標記即可,接下來用 !t 觀察線程狀態。

0:000> !t
ThreadCount:      4
UnstartedThread:  0
BackgroundThread: 3
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1      918 000001FA530317B0  203a220 Preemptive  000001FA574096F8:000001FA5740A5C8 000001fa530273e0 -00001 MTA 
   6    2     37c8 000001FA53009B70    21220 Preemptive  0000000000000000:0000000000000000 000001fa530273e0 -00001 Ukn (Finalizer) 
   7    3     2c7c 000001FA5307F700    2b220 Preemptive  0000000000000000:0000000000000000 000001fa530273e0 -00001 MTA 
   8    4     3bd4 0000023AE951DFD0    2b020 Preemptive  000001FA57563A08:000001FA57565010 000001fa530273e0 -00001 MTA

從卦中可以輕松的看到 DBG=8 的線程狀態是 2b020,自然就是前臺線程咯。

三、總結

現在我們知道了前后臺線程本質上是 coreclr 弄出來的概念,并非系統線程素有之物。還是那句話,知識不重要,重要的是會使用合適的工具和保有的探索心,這也是在訓練營里重度強調的。

責任編輯:武曉燕 來源: 一線碼農聊技術
相關推薦

2021-03-29 00:02:10

C#Attribute元素

2024-01-02 13:26:39

TLSC#線程

2023-12-07 07:26:04

2022-11-02 08:51:01

2024-08-26 14:46:57

2020-10-30 07:11:31

C 語言編程

2024-06-28 12:47:29

C#弱引用底層

2022-08-30 07:39:57

C++namespace隔離

2020-10-23 07:00:00

C++函數

2020-12-29 05:33:40

TomcatSpringBoot代碼

2018-05-16 08:58:04

用戶畫像存儲

2025-01-10 08:15:22

C#異步底層

2020-12-09 16:55:57

程序員技術

2023-03-05 18:40:39

iptables防火墻軟件

2018-11-30 12:48:36

SDS故障硬件

2021-05-12 18:02:23

方法創建線程

2023-09-22 17:36:37

2021-01-28 22:31:33

分組密碼算法

2020-05-22 08:16:07

PONGPONXG-PON

2018-06-07 13:17:12

契約測試單元測試API測試
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人午夜毛片 | 久久精品久久久 | 2021天天躁夜夜看 | 国产视频一区在线观看 | 精品国产aⅴ | 久久久精品一区 | 国产成人网 | 国产免费观看久久黄av片涩av | 视频一区在线观看 | 拍真实国产伦偷精品 | 美女黄视频网站 | 中文字幕在线一区 | 久久精品无码一区二区三区 | 国产精品久久久久久 | 国产精品亚洲综合 | 蜜臀91视频 | 97av视频| 日韩精品久久久久久 | 一区二区久久精品 | 99久久婷婷国产综合精品电影 | 男人的天堂亚洲 | 午夜精品 | 成人精品一区二区三区中文字幕 | 亚欧精品一区 | 免费黄色日本 | 久久福利电影 | 一区二区三区久久 | 亚洲一区久久 | 日本中出视频 | 91国产在线播放 | 91中文字幕在线观看 | 毛片网站在线观看 | 国产日韩欧美另类 | 成人三区四区 | 久久成人一区 | 亚洲视频在线免费观看 | 99re在线 | 超碰8| 欧美视频第二页 | 天天插天天搞 | 成人精品一区亚洲午夜久久久 |