公交车上荫蒂添的好舒服的电影-公用玩物(np双xing总受)-公用小荡货芊芊-公与妇仑乱hd-攻把受做哭边走边肉楼梯play-古装一级淫片a免费播放口

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

一個超經(jīng)典 WinForm,WPF 卡死問題的終極反思

freeflydom
2025年1月15日 9:1 本文熱度 812

一:背景

1. 講故事

寫這篇文章起源于訓(xùn)練營里一位朋友最近在微信聊到他對這個問題使用了一種非常切實可行,簡單粗暴的方式,并且也成功解決了公司里幾個這樣的卡死dump,如今在公司已是靈魂級人物,讓我也嘗到了什么叫反哺!對,這個東西叫 Harmony, github網(wǎng)址: https://github.com/pardeike/Harmony,一個非常牛逼的C#程序函數(shù)修改器。

二:卡死問題的回顧

1. 故障成因

為了方便講述,先把 WinForm/WPF 程序故障的調(diào)用堆棧給大家呈現(xiàn)一下。


0:000:x86> !clrstack
OS Thread Id: 0x4eb688 (0)
Child SP       IP Call Site
002fed38 0000002b [HelperMethodFrame_1OBJ: 002fed38] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
002fee1c 5cddad21 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
002fee34 5cddace8 System.Threading.WaitHandle.WaitOne(Int32, Boolean)
002fee48 538d876c System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
002fee88 53c5214a System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
002fee8c 538dab4b [InlinedCallFrame: 002fee8c] 
002fef14 538dab4b System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
002fef48 53b03bc6 System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback, System.Object)
002fef60 5c774708 Microsoft.Win32.SystemEvents+SystemEventInvokeInfo.Invoke(Boolean, System.Object[])
002fef94 5c6616ec Microsoft.Win32.SystemEvents.RaiseEvent(Boolean, System.Object, System.Object[])
002fefe8 5c660cd4 Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)
002ff008 5c882c98 Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)
...

這個程序之所以被卡死,底層原因到底大概是這樣的。

  1. 程序在t1時間,有非主線程創(chuàng)建了控件。
  2. 程序在t2時間,用戶主動或被動做了 遠程連接,Windows主題色刷新 等操作,這種系統(tǒng)級操作Windows需要同步刷新給所有UI控件。
  3. 那些非主線程控件由于沒有 MessageLoop 機制,導(dǎo)致主線程給這些UI發(fā)消息時得不到響應(yīng),最終引發(fā)悲劇。

t2時間的卡死是由于t1時間的錯誤創(chuàng)建導(dǎo)致,要想在dump中反向追溯目前是無法做到的,所以要想找到禍根需要監(jiān)控t1,即MarshalingControl到底是誰創(chuàng)建的,為此我也寫過兩篇文章來仔細分析此事。

第一種方式是啟動 windbg 對 System_Windows_Forms_ni System.Windows.Forms.Application+MarshalingControl..ctor 進行攔截,說實話這種方式很多程序員搞不定,原因在于windbg的使用門檻較高,現(xiàn)實中很多程序員連CURD都沒摸明白,所以可想而知了。。。

第二種方式是啟動 perfview 對 winform/wpf 程序進行監(jiān)控,直到程序出現(xiàn)卡死停止收集。最后在錄播中尋找 MarshalingControl..ctor 的調(diào)用棧,這種方式也有不可行的時候,如果說卡死發(fā)生在程序啟動的10天后,那這個錄播文件將會超級超級大,或者有更極端的情況發(fā)生。

所以這兩種方案都有各自的優(yōu)缺點,現(xiàn)實可行性雖然有,但不高。。。今天作為終結(jié)篇,必須把這個問題安排掉,繼續(xù)提供兩種切實可行的方案。

三:兩種修改方案

1. 使用 Harmony 注入

Harmony作為一款運行時C#方法修改器,借助它我完全可以將一些邏輯注入到 MarshalingControl..ctor 中,比如記錄下初始化該方法的 堆棧信息 ,是不是就可以輕松找到這個非主線程控件到底是誰?對不對,有了思路,我們在 nuget 上引用 Lib.Harmony ,上代碼說話。


    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            var harmony = new Harmony("一線碼農(nóng)聊技術(shù)");
            Type applicationType = typeof(Application);
            Type marshalingControlType = applicationType.GetNestedType("MarshalingControl", BindingFlags.NonPublic);
            ConstructorInfo constructor = marshalingControlType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
            var prefix = typeof(HookMarshalingControl).GetMethod("OnActionExecuting");
            harmony.Patch(constructor, new HarmonyMethod(prefix));
        }
        private void Form1_Load(object sender, EventArgs e)
        {
        }
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            Button btn = new Button();
            var query = btn.Handle;
        }
        private void button1_Click(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }
    }
    /// <summary>
    /// Hook MarshalingControl 的描述類
    /// </summary>
    public class HookMarshalingControl
    {
        /// <summary>
        /// 原生方法之前執(zhí)行的 action
        /// </summary>
        public static void OnActionExecuting()
        {
            Console.WriteLine("----------------------------");
            Console.WriteLine($"控件創(chuàng)建線程:{Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine(Environment.StackTrace);
            Console.WriteLine("----------------------------");
        }
    }

卦中的代碼邏輯我就不詳述了,核心就是將 OnActionExecuting 方法注入到 MarshalingControl..ctor 構(gòu)造函數(shù)里,把程序運行起來后觀察 output 窗口,截圖如下:

終于是一個臥槽,禍根居然是一個 tid=3 的線程初始化了 new Button() 控件。。。

2. 使用 DnSpy

Harmony 作為一款修改器,它對程序的侵入性是非常高的,目前還是有一些bug,比如對 .NET7 的支持還不是很好,但相對 perfview 和 windbg 的方式已經(jīng)非常輕量級了,極大的降低了使用門檻。

問題來了,那有沒有一種對程序無侵入,可行性超高的方式呢?當(dāng)然是有的,dnspy 此時可以閃亮登場,用過 dnspy 的朋友應(yīng)該知道它是一款輕量級,免安裝綠色的調(diào)試器,當(dāng)然除了調(diào)試器功能,它還是一款程序集修改器,可以實現(xiàn) Harmony 的所有功能,在實踐中我們可以將 dnspy copy 到客戶機使用 啟動調(diào)試 或者 附加進程 的方式對程序進行干預(yù)。

如何使用 dnspy 對 MarshalingControl..ctor 進行干預(yù)呢?可以使用 斷點日志 的功能,日志信息如下:

控件創(chuàng)建線程:{Environment.CurrentManagedThreadId} \n $CALLSTACK

有些人可能要問了 $CALLSTACK 是什么東西?很顯然是堆棧信息,除了這個關(guān)鍵詞還有很多,具體可以看后面的 問號面板

接下來把程序跑起來,觀察 output面板。

從面板中可以清楚的看到,原來有個 tid=3 的線程創(chuàng)建了一個 Button 控件,這就是我們要找的禍根。

到這里,可能有些人要說,dnspy 啟動 exe 的方式因為各種原因在我們這邊行不通,有沒有其他的方式呢? 當(dāng)然是有的,我們還可以在程序啟動之后以 進程附加 的方式注入,同樣也是一種非常可行且低侵入的方式。

為了能夠更早的介入,可以在 Form1 初始化之前彈一個MessageBox,有更好的方式大家也可以說一下,感謝。參考代碼如下:


    public partial class Form1 : Form
    {
        public Form1()
        {
            MessageBox.Show("開啟你的注入吧...");
            InitializeComponent();
        }
    }

彈框之后,使用 dnspy 的進程附加。

附加好了之后關(guān)閉彈框讓程序繼續(xù)運行,點擊 buttton 按鈕,可以看到 output 上的輸出。


11:20:01.548 控件創(chuàng)建線程:<<<當(dāng)線程位于不安全狀態(tài)時無法對表達式進行求值。按步調(diào)試或運行直到觸發(fā)斷點。>>>  
11:20:01.550   	System.Windows.Forms.Application.MarshalingControl.MarshalingControl
11:20:01.551 	System.Windows.Forms.Application.ThreadContext.MarshalingControl.get
11:20:01.552 	System.Windows.Forms.WindowsFormsSynchronizationContext.WindowsFormsSynchronizationContext
11:20:01.553 	System.Windows.Forms.WindowsFormsSynchronizationContext.InstallIfNeeded
11:20:01.553 	System.Windows.Forms.Control.Control
11:20:01.554 	System.Windows.Forms.ButtonBase.ButtonBase
11:20:01.554 	System.Windows.Forms.Button.Button
11:20:01.554 	WindowsFormsApp1.Form1.backgroundWorker1_DoWork
11:20:01.555 	System.ComponentModel.BackgroundWorker.OnDoWork
11:20:01.555 	System.ComponentModel.BackgroundWorker.WorkerThreadStart
11:20:01.556 	System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage
11:20:01.556 	System.Threading.ExecutionContext.RunInternal
11:20:01.557 	System.Threading.ExecutionContext.Run
11:20:01.557 	System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem
11:20:01.557 	System.Threading.ThreadPoolWorkQueue.Dispatch
11:20:01.558 	[本機到托管的轉(zhuǎn)換]
11:20:01.558 	  

這里稍微提醒一下,tid 在這里沒有顯示出來,大家可以換成問號面板上的關(guān)鍵詞 $TID 即可,不過TID不是最重要的,最重要的是調(diào)用棧給弄出來了。

四:總結(jié)

作為一名專業(yè)的 .NET高級調(diào)試師,在這個經(jīng)典卡死的問題溯源上一直沒有提供非常好的解決方案,還是有些內(nèi)疚的,在我的高級調(diào)試之旅中還是會不間斷的收到類似dump,相信這篇文章之后,不再有人被它所困擾!

?轉(zhuǎn)自https://www.cnblogs.com/huangxincheng/p/18668388


該文章在 2025/1/15 9:01:42 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 白丝jk女仆爆乳自慰喷水 | 911亚洲精品国产自产 | 国产成人午夜福利影片 | 国产成人久久精品二区三区小说 | 精品人妻少妇av一区二区三区 | 国产精品免费视频久久 | 成人免费一级纶理 | 国产精品成人一区 | 91羞羞网站| hezyo加勒比久久爱综合 | av天堂午夜精品一区 | 国产精品在线亚洲手机视频 | 国产精品高清一区二区三区 | 国产操对白普通 | 东京无码熟妇人妻av在线网址 | 丰满年轻岳欲乱中文 | 国产三级精品电影视频 | 国自产精品手机在线 | 国产女主播精品视频一区 | 国产成人一区二区三区影院免 | 韩国女主播一区二区视频 | 国产91精品露脸国语对白 | 国产蜜臀av在线一区尤物 | 国产丝袜精品丝袜久久 | 国产福利视频一区二区在线播放 | 国产精品va无码免费 | 国产成人精品怡红院在线观看 | 国产成人综合精品亚洲 | 国产欧美日韩视频怡春院 | 国产精品香港三级国产av | 国产美女精品视频 | 东京热中文字幕a专区 | 国产欧美久久久久久精品一区二区 | 91探花视频在线观看 | 国产熟女高潮 | av无码a在线观看 | 国产午夜视频专区 | 国产经典三级视频在线观看 | 精品无码视频一区三区四区 | 国产黄色大片在线观看激情 | 国产在线一级毛片在线直接看 |