)
簡介面向仍在使用 VS2010 與 .NET Framework 4.0 的 WinForm 開發(fā)者這份示例工程演示如何借助 CefSharp.WinForms 49.0.1 把 Chromium 內(nèi)核嵌入桌面程序替代老舊的 WebBrowser 控件解決網(wǎng)頁兼容性與腳本交互能力不足的問題屬于可直接上手的中初級實戰(zhàn)參考。壓縮包共 319 個文件、約 189.28MB其中 174 個 pak 與 52 個 dll 構(gòu)成 CEF 運行時依賴與本地化資源另有 7 個 cs 源文件、2 個 csproj 工程及 xml、config、bin、exe 等配套項。代碼覆蓋 NuGet 引用配置、Cef.Initialize 初始化、ChromiumWebBrowser 控件創(chuàng)建、加載完成與狀態(tài)變更事件監(jiān)聽、ExecuteScriptAsync 執(zhí)行腳本、CefSettings 定制如禁用 GPU 加速、設(shè)置緩存路徑以及退出時調(diào)用 Cef.Shutdown 釋放資源。工程可直接編譯運行也可作為二次開發(fā)骨架已有 3432 人學(xué)習(xí)下載適合需要快速驗證內(nèi)嵌瀏覽器方案的 .NET 桌面開發(fā)者參考。1. 從一次白屏說起VS2010 .NET 4.0 下還能用的 CefSharp.WinForms 示例這份 C# WinForm CefSharp 示例代碼解決的是 VS2010 .NET 4.0 老項目里嵌一個 Chromium 瀏覽器控件的剛需。前陣子維護(hù)一個模擬項目X客戶端只能跑在 .NET 4.0 上業(yè)務(wù)頁面已經(jīng)是現(xiàn)代前端構(gòu)建出來的WebBrowser 控件一打開就腳本報錯換成 CefSharp 又連續(xù)白屏。后來把版本對齊、初始化順序、輸出目錄和 x86/x64 這幾件事拆開驗證才整理出一套能編譯、能加載本地 HTML、能做 C# 與 JS 互調(diào)的最小骨架。它適合還在維護(hù)老 WinForm 客戶端、不能隨便升級框架的人也適合想先跑通 CefSharp 再疊業(yè)務(wù)邏輯的開發(fā)者。下面按“選包、起工程、調(diào)生命周期、部署、排錯”的順序拆開講代碼按 VS2010 的 C# 4.0 語法寫不依賴 async/await。2. 版本對齊先確認(rèn) CefSharp 包里有 net40再談 WinForm 嵌瀏覽器2.1 為什么 VS2010 下不能隨便拿最新 CefSharp 包VS2010 自帶的是 .NET 4.0 和 C# 4.0這兩個約束會同時卡住 CefSharp 的托管層和示例代碼。CefSharp 的托管程序集有目標(biāo)框架要求較新的包往往把 lib 目錄切到 net45、net452 甚至更高直接引用會提示程序集依賴不兼容C# 4.0 又不支持 async/await網(wǎng)上大量 CefSharp 示例是 C# 5 以后的寫法復(fù)制進(jìn) VS2010 會直接編譯不過。另一個容易被忽略的是原生層CefSharp 依賴 libcef.dll、CefSharp.BrowserSubprocess.exe 和一整套 CEF 資源文件它們分 x86 和 x64項目平臺目標(biāo)設(shè)成 AnyCPU 時很容易把錯誤架構(gòu)的原生 DLL 帶進(jìn)進(jìn)程。我一般選包時先做三件事解壓 nupkg看 lib 目錄下有沒有 net40 或 net4.0 文件夾看 nuspec 里對 .NET Framework 的依賴再看 cef.redist.x86 / cef.redist.x64 包里是不是同時帶了托管 DLL 和原生文件。沒有 net40 目錄的包不要硬上換一個能對上的舊分支比后期改程序集綁定省事。版本號不必記直接看包內(nèi)目錄和 nuspec 最穩(wěn)。選好之后把 nupkg 當(dāng)壓縮包解壓手工引用 DLL比在 VS2010 里折騰老版本 NuGet 更可控。2.2 手工還原一個可編譯的工程骨架在 VS2010 里新建 Windows Forms Application目標(biāo)框架選 .NET Framework 4.0。進(jìn)配置管理器把平臺從 AnyCPU 改成 x86新建平臺時不要勾“創(chuàng)建解決方案的平臺”直接把當(dāng)前項目改成 x86。然后把 CefSharp 包里的 CefSharp.dll、CefSharp.Core.dll、CefSharp.WinForms.dll 加到引用引用屬性里的 Copy Local 設(shè)為 True。原生文件不通過引用走直接復(fù)制到項目下的 CefSharp 目錄再在項目里包含這些文件屬性設(shè)為“如果較新則復(fù)制”。!-- 項目文件里重點看這幾項等價于在 VS2010 屬性面板里設(shè)置 -- PropertyGroup TargetFrameworkVersionv4.0/TargetFrameworkVersion PlatformTargetx86/PlatformTarget Prefer32Bitfalse/Prefer32Bit /PropertyGroup這段配置的核心是 TargetFrameworkVersion 和 PlatformTarget。VS2010 的 WinForm 項目模板默認(rèn)可能是 AnyCPUCefSharp 在 64 位系統(tǒng)上會按進(jìn)程位數(shù)去找原生 DLL如果輸出目錄里只有 x86 的 libcef.dll就會出現(xiàn)進(jìn)程啟動后白屏或直接退出。Prefer32Bit 是給后續(xù)高版本 MSBuild 看的VS2010 下主要靠 PlatformTarget 控制。平臺目標(biāo)定下來之后輸出目錄只放對應(yīng)架構(gòu)的 CEF 文件不要 x86 和 x64 混放混放時誰先被加載看運氣玄學(xué)問題會很多。2.3 最小驗證先加載 about:blank再加載本地 HTML工程骨架起來后不要急著接業(yè)務(wù)頁面。先寫一個最小窗體在 Load 事件里動態(tài)創(chuàng)建 ChromiumWebBrowser加載 about:blank確認(rèn)能出空白頁而不是白屏。這個步驟能過濾掉大部分原生 DLL 和子進(jìn)程路徑問題。確認(rèn) about:blank 正常后再把 URL 換成 Application.StartupPath 下的 index.html。本地文件路徑用 file:/// 前綴反斜杠換成斜杠中文目錄先避開。private void MainForm_Load(object sender, EventArgs e) { // 先加載 about:blank確認(rèn) CefSharp 原生層和子進(jìn)程都正常 var browser new ChromiumWebBrowser(about:blank); browser.Dock DockStyle.Fill; this.Controls.Add(browser); // 原生層確認(rèn)后再換成本地 HTML string htmlPath Application.StartupPath.Replace(\\, /) /index.html; browser.Load(file:/// htmlPath); }這段代碼沒有用設(shè)計器拖控件原因是 ChromiumWebBrowser 在設(shè)計時初始化會拉子進(jìn)程VS2010 設(shè)計器經(jīng)常直接崩。動態(tài)創(chuàng)建放在 Form.Load 里設(shè)計器只看到普通窗體打開設(shè)計器不會觸發(fā) Cef 初始化。Dock 設(shè)為 Fill 讓瀏覽器鋪滿窗體Load 方法傳 URL 即可。如果 about:blank 都白屏先別改代碼去輸出目錄檢查 libcef.dll、CefSharp.BrowserSubprocess.exe 和 locales 是否齊全再看項目平臺目標(biāo)和 DLL 架構(gòu)是否一致。3. 把瀏覽器控件用起來初始化順序、生命周期和 C# 與 JS 互調(diào)3.1 Cef.Initialize 和 Cef.Shutdown 的配對Cef.Initialize 建議放在 Program.Main 里、Application.Run 之前并且全進(jìn)程只調(diào)用一次。常見錯誤是在窗體構(gòu)造函數(shù)里初始化每開一個窗體調(diào)一次第二次就拋異常或者初始化放在 Load 里子進(jìn)程路徑還沒準(zhǔn)備好。初始化參數(shù)里 BrowserSubprocessPath 必須指向輸出目錄下的 CefSharp.BrowserSubprocess.exeCachePath 和 UserDataPath 指向有寫權(quán)限的目錄。老項目裝在 Program Files 下時Application.StartupPath 往往不可寫緩存會靜默失敗表現(xiàn)就是頁面加載一半或登錄態(tài)丟失。[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 舊分支的 JS 綁定開關(guān)必須在 Cef.Initialize 之前設(shè)置 CefSharpSettings.LegacyJavascriptBindingEnabled true; var settings new CefSettings(); settings.BrowserSubprocessPath Path.Combine(Application.StartupPath, CefSharp.BrowserSubprocess.exe); settings.CachePath Path.Combine(Application.StartupPath, Cache); settings.UserDataPath Path.Combine(Application.StartupPath, UserData); settings.Locale zh-CN; settings.LogSeverity LogSeverity.Info; settings.LogFile Path.Combine(Application.StartupPath, cef.log); try { Cef.Initialize(settings); } catch (Exception ex) { MessageBox.Show(Cef 初始化失敗 ex.Message); return; } Application.Run(new MainForm()); }參數(shù)里 BrowserSubprocessPath 是最容易漏的不設(shè)時 CefSharp 會按默認(rèn)規(guī)則找子進(jìn)程部署目錄一變就找不到。CachePath 和 UserDataPath 分開緩存和用戶數(shù)據(jù)不要塞進(jìn)只讀目錄。Locale 設(shè)成 zh-CN 對中文界面和輸入法更穩(wěn)。LogSeverity 和 LogFile 是排錯用的白屏?xí)r先看 cef.log 里有沒有“failed to load libcef”或子進(jìn)程啟動失敗。CefSharpSettings.LegacyJavascriptBindingEnabled 只對舊版 RegisterJsObject 生效新版綁定 API 不需要但老分支不設(shè)置會報對象未定義。3.2 ChromiumWebBrowser 的創(chuàng)建時機與釋放ChromiumWebBrowser 的創(chuàng)建放在窗體 Load 事件里比較穩(wěn)FormClosing 里先 Dispose 瀏覽器再 Cef.Shutdown。順序反了會出現(xiàn)關(guān)閉窗體后 CefSharp.BrowserSubprocess.exe 還在任務(wù)管理器里第二次打開程序連不上子進(jìn)程。Dispose 之后把引用置空避免 FormClosing 被多次觸發(fā)時重復(fù)釋放。如果窗體里有多個瀏覽器實例每個實例都要 Dispose但 Cef.Shutdown 全進(jìn)程只調(diào)一次。private ChromiumWebBrowser browser; private void MainForm_Load(object sender, EventArgs e) { browser new ChromiumWebBrowser(about:blank); browser.Dock DockStyle.Fill; this.Controls.Add(browser); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { if (browser ! null) { browser.Dispose(); browser null; } Cef.Shutdown(); }這段代碼的關(guān)鍵是生命周期配對。Load 里創(chuàng)建FormClosing 里釋放Cef.Shutdown 放在窗體關(guān)閉時而不是 Application.Exit 事件里避免窗體還沒釋放就先關(guān) Cef。Dock 設(shè)為 Fill 后不需要再調(diào) Anchor。如果窗體里還有其他控件建議給瀏覽器一個 Panel 容器瀏覽器 Dock 到 Panel業(yè)務(wù)控件放在 Panel 外面后面換皮膚或加工具欄時不用改瀏覽器布局。3.3 JS 調(diào) C#RegisterJsObject 的綁定時機與命名舊分支常用 RegisterJsObject 把 C# 對象暴露給頁面。綁定要放在瀏覽器創(chuàng)建之后、頁面 Load 之前綁定太晚頁面腳本已經(jīng)執(zhí)行完window.bridge 就是 undefined。第三個參數(shù) camelCaseJavascriptNames 決定 C# 方法名是否轉(zhuǎn)成首字母小寫傳 false 時 JS 里保持 C# 原名傳 true 時 GetVersion 變成 getVersion。這個參數(shù)和前端約定必須一致否則前端按小寫調(diào)、后端按大寫綁控制臺只會報未定義。public class JsBridge { public string GetVersion() { return 1.0; } public void Notify(string message) { // JS 調(diào)用可能不在 UI 線程更新控件前用 BeginInvoke } } private void MainForm_Load(object sender, EventArgs e) { browser new ChromiumWebBrowser(about:blank); browser.Dock DockStyle.Fill; this.Controls.Add(browser); // 第三個參數(shù) falseJS 里用 window.bridge.GetVersion() browser.RegisterJsObject(bridge, new JsBridge(), false); browser.Load(file:/// Application.StartupPath.Replace(\\, /) /index.html); }JsBridge 里的方法盡量簡單參數(shù)用 string、int、bool 這類基礎(chǔ)類型復(fù)雜對象讓前端序列化成 JSON 字符串再傳減少綁定層序列化差異。Notify 里如果要更新 WinForm 控件不要直接改JS 調(diào)用線程不一定是 UI 線程用 BeginInvoke 包一層。注冊對象名不要用 window、document 這類保留字也不要和頁面已有全局變量重名重名時頁面自己的對象會被覆蓋排查起來很費時間。3.4 C# 調(diào) JS不用 async/await 的 EvaluateScriptAsync 寫法VS2010 的 C# 4.0 不支持 async/await但 EvaluateScriptAsync 返回的是 Task可以用 ContinueWith 接。回調(diào)里判斷 task.IsFaulted、task.IsCanceled再取 task.Result.Success 和 task.Result.Result。ContinueWith 的第二個參數(shù)傳 TaskScheduler.FromCurrentSynchronizationContext()讓回調(diào)回到 UI 線程避免在回調(diào)里直接操作控件時拋跨線程異常。這個寫法比 Thread.Sleep 等頁面加載可靠得多。private void ReadDocumentTitle() { browser.EvaluateScriptAsync(document.title).ContinueWith(task { if (task.IsFaulted || task.IsCanceled) { return; } var response task.Result; if (response.Success) { MessageBox.Show(Convert.ToString(response.Result)); } }, TaskScheduler.FromCurrentSynchronizationContext()); }EvaluateScriptAsync 的參數(shù)是 JavaScript 表達(dá)式返回值會走 JSON 序列化字符串、數(shù)字、布爾值都能直接轉(zhuǎn)DOM 對象不要直接返回先轉(zhuǎn)成 JSON.stringify 再傳回來。ContinueWith 里不要假設(shè) task.Result 一定不為空腳本拋異常時 Success 為 falseResult 可能是 null。需要連續(xù)調(diào)多個腳本時不要嵌套 ContinueWith容易把線程調(diào)度繞暈拆成多個方法按業(yè)務(wù)順序調(diào)或者在前端用一個初始化完成標(biāo)記C# 側(cè)只探這一個標(biāo)記。4. 部署不是復(fù)制一個 exe輸出目錄、x86/x64 與 VC 運行時的核對表4.1 輸出目錄該放哪些文件CefSharp 的部署目錄不是只有一個 exe托管 DLL、原生 DLL、子進(jìn)程、資源文件、語言包都要在。不同分支文件名單會變最穩(wěn)的辦法是看 cef.redist 包里的 README 或示例輸出目錄把那一套整體復(fù)制過來再刪掉不需要的語言包。下面這張表是常見必須項用來核對有沒有漏不是固定清單。文件或目錄作用常見漏法CefSharp.dll托管公共接口只引用了 WinForms 包漏了公共接口CefSharp.Core.dll托管核心引用有輸出目錄沒有CefSharp.WinForms.dllWinForms 控件項目引用正常發(fā)布時漏復(fù)制CefSharp.BrowserSubprocess.exe瀏覽器子進(jìn)程沒設(shè) BrowserSubprocessPath或沒復(fù)制libcef.dllCEF 原生核心架構(gòu)不對或被殺軟隔離chrome_elf.dll加載與崩潰輔助只復(fù)制 libcef.dll漏了它icudtl.datICU 數(shù)據(jù)頁面中文亂碼或直接白屏natives_blob.bin、snapshot_blob.binV8 快照啟動時報快照加載失敗locales 目錄語言包只放 en-US.pak中文環(huán)境異常swiftshader 或 d3dcompiler_47.dll軟件渲染與圖形依賴虛擬機或無 GPU 環(huán)境黑屏核對時不要只看項目引用列表要看 bin 目錄實際輸出。VS2010 里非引用文件靠“復(fù)制到輸出目錄”屬性控制有時候文件在項目里屬性還是“不復(fù)制”調(diào)試時從 obj 目錄能跑發(fā)布就缺文件。建議在項目里建一個 CefSharp 文件夾把原生文件全部放進(jìn)去屬性統(tǒng)一設(shè)成“如果較新則復(fù)制”發(fā)布時整目錄帶走。4.2 平臺目標(biāo)與原生 DLL 架構(gòu)AnyCPU 為什么容易翻車AnyCPU 在 64 位系統(tǒng)上默認(rèn)跑 64 位進(jìn)程但很多 CefSharp 包默認(rèn)帶的是 x86 原生文件進(jìn)程位數(shù)和 DLL 架構(gòu)對不上libcef.dll 加載失敗表現(xiàn)就是窗體出來但瀏覽器區(qū)域全白。反過來項目設(shè)了 x64輸出目錄卻是 x86 的 cef.redist也一樣白屏。排查時先看任務(wù)管理器里進(jìn)程有沒有“32 位”標(biāo)記再用 dumpbin 看 libcef.dll 的 machine 字段兩邊必須一致。:: 在 Visual Studio 命令提示符里執(zhí)行查看 libcef.dll 是 x86 還是 x64 dumpbin /headers libcef.dll | findstr machine輸出里 14C 表示 x868664 表示 x64。項目平臺目標(biāo)設(shè) x86 時輸出目錄只放 x86 的 CEF 文件設(shè) x64 時只放 x64。不要為了兼容兩種機器在一個目錄里混放CefSharp 不會自動按位數(shù)切目錄混放時加載到哪個看文件系統(tǒng)順序。確實要同時支持 32 位和 64 位就出兩個安裝包或者啟動器里判斷 IntPtr.Size 后切工作目錄普通業(yè)務(wù)客戶端沒必要做這么復(fù)雜。4.3 VC 運行時與日志白屏?xí)r先看 cef.logCEF 原生層依賴 VC 運行時缺運行庫時進(jìn)程可能直接退出也可能彈一個系統(tǒng)錯誤框后白屏。不要憑記憶裝年份用 Dependencies 或 dumpbin /dependents 看 libcef.dll 依賴哪些 msvcp、vcruntime缺哪個裝哪個x86 和 x64 分開裝。裝完重啟一下客戶端再做驗證有時候運行庫剛裝完當(dāng)前進(jìn)程還沒重新加載。var settings new CefSettings(); settings.LogSeverity LogSeverity.Info; settings.LogFile Path.Combine(Application.StartupPath, cef.log);日志文件要放在有寫權(quán)限的目錄Program Files 下普通用戶不可寫時日志不會生成看起來像“沒日志”實際是寫不進(jìn)去。cef.log 里重點看 libcef.dll 加載路徑、BrowserSubprocess 啟動參數(shù)、GPU 初始化失敗、字體加載失敗。GPU 相關(guān)報錯可以先加settings.DisableGpu true驗證是不是顯卡驅(qū)動或虛擬機渲染問題確認(rèn)后再決定是否長期禁用。日志級別不要長期開 Verbose文件漲得很快排完障改回 Info 或 Disable。5. 避坑與排查白屏、設(shè)計器閃退、JS 未定義、進(jìn)程殘留的 5 條記錄5.1 設(shè)計器一打開就崩別把 ChromiumWebBrowser 拖進(jìn)設(shè)計器現(xiàn)象是雙擊 MainForm.cs 進(jìn)設(shè)計器VS2010 停幾秒后直接退出或者彈“未將對象引用設(shè)置到對象的實例”。原因是 ChromiumWebBrowser 在設(shè)計時也會走構(gòu)造函數(shù)觸發(fā) Cef 初始化和子進(jìn)程創(chuàng)建設(shè)計器進(jìn)程里沒有完整的 CEF 運行環(huán)境直接崩。解決方式是窗體上不放瀏覽器控件在 Load 事件里用代碼創(chuàng)建設(shè)計器里只留一個 Panel 占位。已經(jīng)拖進(jìn)去的把控件從設(shè)計器刪掉構(gòu)造函數(shù)里的初始化代碼移到 Load 里重新生成后再開設(shè)計器。5.2 運行后白屏或黑屏先查 libcef.dll 和架構(gòu)現(xiàn)象是窗體正常出來瀏覽器區(qū)域全白cef.log 里可能有“failed to load libcef.dll”或“browser subprocess exited”。原因按概率排輸出目錄沒有 libcef.dlllibcef.dll 架構(gòu)和進(jìn)程位數(shù)不一致BrowserSubprocessPath 沒設(shè)置或路徑不對VC 運行時缺失。解決順序是先看輸出目錄文件齊不齊再用 dumpbin 看架構(gòu)然后確認(rèn) BrowserSubprocessPath 指向?qū)嶋H exe最后用 Dependencies 查 libcef.dll 的依賴。四項都過還白屏把 LogSeverity 開到 Verbose看日志里第一個 error 出現(xiàn)在哪一步不要從最后一個報錯往前猜。5.3 編譯報 async/await 錯誤VS2010 的 C# 4.0 不認(rèn)現(xiàn)象是從網(wǎng)上復(fù)制的 CefSharp 示例粘貼進(jìn)項目后async、await關(guān)鍵字下面全是紅線錯誤提示“找不到類型或命名空間名稱 async”之類。原因是 VS2010 默認(rèn) C# 4.0async/await 是 C# 5 才有的語法。解決方式不是硬升框架而是把異步調(diào)用改成 ContinueWith 寫法EvaluateScriptAsync 返回 Task用 TaskScheduler.FromCurrentSynchronizationContext() 回 UI 線程。如果項目里大量 async 代碼考慮單獨抽一個 C# 5 的輔助程序集但老項目里通常不值得按最小改動改成回調(diào)更穩(wěn)。5.4 JS 調(diào)用 C# 報 undefined綁定時機和命名踩坑現(xiàn)象是頁面控制臺報window.bridge is undefined或者bridge.GetVersion is not a function。原因常見三種RegisterJsObject 在頁面 Load 之后才調(diào)用camelCaseJavascriptNames 參數(shù)和前端調(diào)用大小寫不一致LegacyJavascriptBindingEnabled 沒在 Cef.Initialize 之前打開。解決方式是先確認(rèn)綁定代碼在 Load 之前執(zhí)行再在頁面里打印Object.keys(window.bridge)看實際暴露了哪些方法最后核對注冊時第三個參數(shù)。舊分支的 RegisterJsObject 對綁定對象名敏感名字里不要帶點、橫線前端調(diào)用時對象名和方法名分開檢查。5.5 關(guān)閉窗體后進(jìn)程殘留Cef.Shutdown 與 Dispose 的順序現(xiàn)象是主窗體關(guān)了任務(wù)管理器里 CefSharp.BrowserSubprocess.exe 還在再次啟動程序時頁面加載失敗或直接白屏。原因是 ChromiumWebBrowser 沒 Dispose或者 Cef.Shutdown 調(diào)用太早/太晚子進(jìn)程沒收到退出信號。解決方式是在 FormClosing 里先 browser.Dispose()把引用置空再 Cef.Shutdown()。如果窗體是多文檔結(jié)構(gòu)每個瀏覽器實例都要 DisposeCef.Shutdown 只調(diào)一次。調(diào)試時可以在 Shutdown 前后打日志確認(rèn)順序不要用 Application.Exit 直接殺進(jìn)程。6. 進(jìn)階技巧用 BrowserInitializedChanged 和請求頭把老控件跑穩(wěn)老項目里最容易寫壞的是“等頁面加載”這段邏輯。很多人用 Thread.Sleep(3000) 然后 EvaluateScriptAsync機器慢的時候頁面沒好機器快的時候白等三秒。我一般用 BrowserInitializedChanged 事件確認(rèn)瀏覽器底層初始化完成再用 FrameLoadEnd 判斷主框架加載結(jié)束最后用 EvaluateScriptAsync 探 document.readyState。三層判斷比固定睡眠穩(wěn)也不會把 UI 線程卡住。下面這段只保留關(guān)鍵分支事件簽名以你包內(nèi) API 為準(zhǔn)。browser.BrowserInitializedChanged (sender, args) { if (!browser.IsBrowserInitialized) { return; } // 初始化完成后再加載業(yè)務(wù)頁面避免在 about:blank 階段搶跑 browser.Load(file:/// Application.StartupPath.Replace(\\, /) /index.html); }; browser.FrameLoadEnd (sender, args) { if (!args.Frame.IsMain) { return; } // 主框架加載完再探 readyState比固定 Sleep 可靠 browser.EvaluateScriptAsync(document.readyState).ContinueWith(task { if (task.IsFaulted || task.IsCanceled || !task.Result.Success) { return; } // 這里的返回值可以記日志也可以觸發(fā)后續(xù)業(yè)務(wù)初始化 }, TaskScheduler.FromCurrentSynchronizationContext()); };另一個實用技巧是給所有請求加自定義頭。老客戶端經(jīng)常需要后端識別“這是 WinForm 內(nèi)嵌瀏覽器”但又不想改頁面代碼。在自定義 IRequestHandler 的 OnBeforeResourceLoad 里給 request.Headers 塞一個固定鍵值返回 false 表示繼續(xù)請求。這樣后端按頭分流前端頁面不用動。注意舊分支 IRequestHandler 接口成員較多除了關(guān)鍵方法其余成員按接口定義返回默認(rèn)值不要只實現(xiàn)一個方法就編譯。public class CustomRequestHandler : IRequestHandler { public bool OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) { // 給每個請求打標(biāo)后端據(jù)此識別客戶端類型 request.Headers[X-Client] WinFormCef; return false; } // 其余接口成員按包內(nèi)定義補全默認(rèn)返回 false 或繼續(xù) }緩存目錄也值得單獨說一下。默認(rèn)把 CachePath 放在 Application.StartupPath 下調(diào)試時沒問題裝到 Program Files 后普通用戶沒有寫權(quán)限緩存寫入失敗不一定報錯表現(xiàn)是頁面加載慢、登錄態(tài)丟、靜態(tài)資源重復(fù)下載。我一般把 CachePath 和 UserDataPath 改到 LocalApplicationData 下按產(chǎn)品名建子目錄啟動時先 Directory.CreateDirectory再傳給 CefSettings。這樣升級安裝包不會把緩存清掉也不會因為權(quán)限問題出現(xiàn)“有時好有時壞”的玄學(xué)。驗證時加載一個帶版本號的靜態(tài)頁刷新兩次看 Network 里有沒有 304或者看緩存目錄有沒有實際文件生成。從那以后我每次接老 WinForm 嵌瀏覽器的活都先做一個只有 about:blank 的最小 Demo確認(rèn) libcef.dll 架構(gòu)、BrowserSubprocessPath、VC 運行時和 Cef.Shutdown 這四件事都過再往里面疊業(yè)務(wù)頁面和 JS 互調(diào)。最小 Demo 跑不通時不要改業(yè)務(wù)代碼先看 cef.log 和輸出目錄能省掉大量來回猜的時間。希望幫到你。本文還有配套的精品資源點擊獲取