【.Net技術棧梳理】04-核心框架與運行時(線程處理)

文章目錄

  • 1. 線程管理
    • 1.1 線程的核心概念:System.Threading.Thread
    • 1.2 現代線程管理:System.Threading.Tasks.Task 和 Task Parallel Library (TPL)
    • 1.3 狀態管理和異常處理
    • 1.4 協調任務:async/await 模式
  • 2. 線程間通信
    • 2.1 共享內存與競態條件
    • 2.2 同步原語:確保線程安全
    • 2.3 線程安全的數據結構
  • 2. 多線程編程模式與最佳實踐
    • 2.1 模式
    • 2.1 最佳實踐與常見陷阱
    • 2.2 補充

1. 線程管理

.NET 的線程管理建立在操作系統線程之上,但提供了更高級別的抽象和更豐富的功能來簡化并發編程。

1.1 線程的核心概念:System.Threading.Thread

這是最基礎的線程類,直接包裝了操作系統線程。

using System.Threading;// 創建并啟動一個新線程
Thread newThread = new Thread(WorkerMethod);
newThread.Start(); // 開始執行 WorkerMethodvoid WorkerMethod()
{Console.WriteLine($"我在另一個線程上運行!線程ID: {Thread.CurrentThread.ManagedThreadId}");
}
  • 前臺線程 vs. 后臺線程:
    • 前臺線程:默認創建的線程是前臺線程。只要有一個前臺線程還在運行,應用程序進程就不會終止。
    • 后臺線程:通過 IsBackground = true 設置。當所有前臺線程結束時,CLR 會強制終止所有后臺線程,無論其是否執行完畢。適用于非關鍵任務(如心跳檢測、日志刷新)。
Thread bgThread = new Thread(WorkerMethod);
bgThread.IsBackground = true;
bgThread.Start();
  • 線程狀態:通過 ThreadState 枚舉表示(Unstarted, Running, WaitSleepJoin, Stopped 等)。
  • 線程池:直接創建和銷毀線程開銷很大。.NET 提供了一個線程池來管理一組重用的工作線程。

1.2 現代線程管理:System.Threading.Tasks.Task 和 Task Parallel Library (TPL)

從 .NET 4.0 開始,Task 成為了推薦的多線程和異步編程模型。它是對 Thread 的高級封裝,極大地簡化了復雜操作。

  • 什么是 Task: 表示一個異步操作。它不一定映射到獨占的操作系統線程。它可能在線程池線程上運行,也可能使用 I/O 完成端口等機制,效率更高。
  • 線程池: Task 默認使用線程池中的線程。線程池會智能地管理線程數量,根據系統負載創建和銷毀線程,避免了頻繁創建新線程的開銷。
  • 創建和啟動 Task
// 方式一:Task.Run (最常用,用于將工作排到線程池)
Task task = Task.Run(() =>
{Console.WriteLine($"Task 在線程池線程上運行。線程ID: {Thread.CurrentThread.ManagedThreadId}");// 模擬工作Thread.Sleep(1000);
});// 方式二:Task.Factory.StartNew (提供更多選項)
Task task2 = Task.Factory.StartNew(() => { /* ... */ }, TaskCreationOptions.LongRunning); // 提示線程池這可能是個長任務// 等待任務完成
task.Wait(); // 阻塞當前線程,直到 task 完成

1.3 狀態管理和異常處理

  • 狀態查詢: Task.Status 屬性(Created, WaitingToRun, Running, RanToCompletion, Faulted, Canceled)。
  • 返回值: Task 可以返回值。
Task<int> calculationTask = Task.Run(() => CalculateSomething());
int result = calculationTask.Result; // 獲取結果(如果任務未完成,會阻塞當前線程)
  • 異常處理: Task 中的異常會被捕獲并存儲在 Task.Exception 屬性中(一個 AggregateException)。當你調用 .Wait(), .Result, 或 .WaitAll() 時,這些異常會被重新拋出
try
{task.Wait(); // 或者訪問 task.Result
}
catch (AggregateException ae)
{foreach (var e in ae.InnerExceptions){Console.WriteLine($"Exception: {e.Message}");}
}

1.4 協調任務:async/await 模式

這是現代 .NET 異步編程的基石,它讓異步代碼看起來像同步代碼一樣直觀。

  • async: 修飾方法,表明該方法包含異步操作。
  • await: 用于等待一個 Task 完成。在 await 時,當前線程會被釋放回線程池,而不是被阻塞。當 Task 完成后,該方法會在線程池線程上恢復執行。
public async Task<int> GetWebsiteLengthAsync(string url)
{// 注意:不要在生產環境使用 HttpClient 這種方式,這里僅為示例。using (var httpClient = new HttpClient()){// await 會釋放當前線程(如UI線程),去處理其他工作(如響應用戶點擊)string content = await httpClient.GetStringAsync(url);// 當下載完成后,執行會在這里恢復(可能在另一個線程池線程上)return content.Length;}
}// 調用異步方法
async void Button_Click(object sender, EventArgs e)
{int length = await GetWebsiteLengthAsync("https://example.com");MessageBox.Show($"Length is: {length}");
}

優勢

  • 非阻塞: 在等待 I/O 操作(如網絡請求、文件讀寫)時,不占用任何線程, scalability(可擴展性)極高。
  • 清晰的代碼流: 避免了復雜的回調地獄(Callback Hell)。

2. 線程間通信

當多個線程需要訪問共享數據或協調行動時,就需要線程間通信。核心挑戰是線程安全。

2.1 共享內存與競態條件

最簡單的通信方式是共享變量,但這會導致競態條件。

private int _counter = 0;void UnsafeIncrement()
{_counter++; // 這不是原子操作,可能被線程切換打斷
}

2.2 同步原語:確保線程安全

.NET 提供了豐富的同步原語來控制對共享資源的訪問。

  • lock 語句(Monitor 類): 最常用的機制,確保代碼塊在任何時候只被一個線程執行。
private readonly object _lockObject = new object();
private int _safeCounter = 0;void SafeIncrement()
{lock (_lockObject) // 一次只允許一個線程進入{_safeCounter++;}
}

注意: 鎖定對象應為 private readonly 的引用類型。

  • Interlocked 類: 提供簡單的原子操作,性能比 lock 更高。
Interlocked.Increment(ref _safeCounter); // 原子性地 +1
Interlocked.Exchange(ref _value, newValue); // 原子性地交換值
  • Mutex 和 Semaphore:
    • Mutex: 類似于 lock,但可以跨進程使用(系統級鎖)。
    • Semaphore / SemaphoreSlim: 允許指定數量的線程同時訪問一個資源池。例如,限制只有 5 個線程可以同時訪問數據庫。
  • ManualResetEvent / AutoResetEvent: 用于線程間的信號通知。一個線程可以 WaitOne() 等待信號,另一個線程可以 Set() 發出信號。
  • Barrier / CountdownEvent: 用于協調多個線程,讓它們在某個點同步。

2.3 線程安全的數據結構

.NET 在 System.Collections.Concurrent 命名空間中提供了一系列線程安全的集合。

  • ConcurrentDictionary<TKey, TValue>
  • ConcurrentQueue< T>
  • ConcurrentStack< T>
  • ConcurrentBag< T>
  • BlockingCollection< T>

這些集合內部實現了高效的同步機制,可以在大多數情況下避免手動加鎖。

private ConcurrentDictionary<string, int> _userScores = new ConcurrentDictionary<string, int>();void UpdateScore(string userId, int points)
{// 無需手動加鎖!_userScores.AddOrUpdate(userId, points, (key, oldValue) => oldValue + points);
}

2. 多線程編程模式與最佳實踐

2.1 模式

  • 生產者/消費者模式: 一個或多個線程(生產者)生成數據并放入共享隊列,一個或多個線程(消費者)從隊列中取出并處理數據。可以使用 BlockingCollection 輕松實現。
  • Fork/Join 模式: 將一個大任務拆分成多個小任務(Fork),并行執行,最后等待所有結果并合并(Join)。Parallel.For/ForEach 和 Task.WhenAll 是實現此模式的利器。

2.1 最佳實踐與常見陷阱

  1. 避免死鎖
    • 原因: 兩個或更多線程互相等待對方釋放鎖。
    • 預防: 按固定的全局順序獲取鎖;使用 Monitor.TryEnter 并設置超時;盡量減少鎖的持有時間。
  2. 警惕線程池的過度訂閱: 不要創建成千上萬的短時 Task,這會導致線程池創建大量線程,上下文切換開銷巨大。對于 CPU 密集型任務,任務數量不應大幅超過 CPU 核心數。
  3. 不要阻塞線程池線程: 在線程池線程上執行同步的 I/O 操作或長時間 CPU 計算會耗盡線程池,影響整個應用程序的響應能力。對于 I/O 操作,始終使用 async/await。
    4.** 使用 Cancellation Tokens**: 提供一種標準機制來取消異步操作。
var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;Task longRunningTask = Task.Run(() =>
{while (true){token.ThrowIfCancellationRequested(); // 如果取消請求了,則拋出 OperationCanceledException// ... 做一點工作}
}, token);// 在某個地方取消操作
cts.CancelAfter(5000); // 5秒后取消
  1. 訪問 UI 控件: 在 WPF/WinForms 中,只有 UI 線程才能更新 UI 控件。從非 UI 線程更新 UI 會引發異常。必須使用 Dispatcher.Invoke (WPF) 或 Control.Invoke (WinForms) 來封送調用回 UI 線程。
// 在 WPF 中
await Task.Run(() => DoHeavyWork());
// 現在回到 UI 線程了,可以安全更新 UI
textBox.Text = "Done!";// 如果在另一個上下文中,需要顯式調用 Dispatcher
Dispatcher.Invoke(() => { textBox.Text = "Done!"; });

總結

  • 基礎: 理解 Thread 類。
  • 現代方式: 優先使用 Task 和 TPL,默認使用線程池,效率更高。
  • 異步 I/O: 對于 I/O 密集型操作,始終使用 async/await,以釋放線程,獲得極高的可擴展性。
  • 線程安全: 使用 lock、Interlocked 或并發集合來保護共享數據。
  • 協調與通信: 使用同步原語(如 Event、Barrier)和模式(生產者/消費者)來協調多線程工作。
  • 避免陷阱: 警惕死鎖、過度訂閱和阻塞線程池線程。

2.2 補充


線程間通信的本質是什么?
多個線程在同一個進程內運行,共享進程的整個內存空間。因此,從廣義上講,任何一個線程寫入內存的數據,理論上都可以被其他線程讀取到。

所以,線程間通信的“通信”二字,其本質是:

  1. 數據傳遞:一個線程生產/計算出的數據,如何安全地交給另一個線程處理。
  2. 狀態同步:一個線程如何知道另一個線程已經完成了某項工作或進入了某種狀態。
  3. 協調行動:多個線程如何步調一致地協作,避免“混亂”(如競態條件)和“死等”(如死鎖)。

核心挑戰:由于操作系統線程調度的不確定性,你永遠不知道一個線程在執行到哪條指令時會被掛起,另一個線程會開始執行。這種交錯執行如果處理不當,就會導致數據損壞、結果錯誤等線程安全問題。

如何進行線程間通信?
.NET 提供了多種機制來實現安全高效的線程間通信,主要分為三大類:

  1. 共享內存(最常用,但最危險)
    這是最直觀的方式:多個線程讀寫同一個變量或數據結構。
  • 如何進行:簡單地創建一個所有線程都能訪問的字段、屬性或靜態變量。
  • 巨大風險:直接共享內存會引發競態條件。
// 危險的共享內存示例
public class UnsafeExample
{private int _counter = 0; // 共享內存public void Increment(){_counter++; // 這不是原子操作!// 它可能被分解為:讀取 -> 加1 -> 寫入// 線程A可能在“讀取”后被打斷,線程B也完成了“讀取”,然后兩者都寫入,導致只加了一次。}
}
  • 如何安全地使用:必須使用同步原語來保護對共享內存的訪問,確保某一時刻只有一個線程能操作它。

    • lock 語句:最常用的工具。
    private readonly object _lockObj = new object();
    private int _safeCounter = 0;public void SafeIncrement()
    {lock (_lockObj) // 一次只允許一個線程進入此代碼塊{_safeCounter++;}
    }
    
    • Interlocked 類:為簡單的數學操作提供原子性,性能更高。
    Interlocked.Increment(ref _safeCounter); // 原子性地完成整個“讀取-修改-寫入”操作
    
    • Monitor 類:lock 語句的底層實現。
    • Mutex:類似于鎖,但可以跨進程使用。
  1. 信號機制(用于協調和通知)
    當一個線程需要“等待”另一個線程完成某項工作后才能繼續時,就需要信號機制。它不直接傳遞數據,而是傳遞“事件已發生”的信號。
  • 如何進行:一個線程等待一個信號,另一個線程發出信號。
  • 常見類型:
    • EventWaitHandle 及其子類:
      • AutoResetEvent:像一個旋轉門,一次只允許一個線程通過。Set() 一次只釋放一個等待的線程,然后自動重置為無信號狀態。
      • ManualResetEvent:像一個大門,Set() 打開大門,釋放所有等待的線程;直到調用 Reset() 才會關上大門。
        // 使用 AutoResetEvent 進行線程協調
        AutoResetEvent _waitHandle = new AutoResetEvent(false); // 初始狀態為無信號void ThreadA()
        {// 做一些準備工作..._waitHandle.Set(); // 發出信號:“我的工作完成了,你可以繼續了”
        }void ThreadB()
        {// 等待 ThreadA 的準備信號_waitHandle.WaitOne(); // 阻塞在此,直到收到信號// 收到信號,繼續執行...
        }
        
    • Semaphore / SemaphoreSlim:類似于一個計數器,用于控制同時訪問某一資源的線程數量。例如,只允許 3 個線程同時訪問數據庫連接池。
    • Barrier:用于讓多個線程在某個時間點同步,所有線程都到達這個點后,才一起繼續執行。適合分階段計算的場景。
    • CountdownEvent:初始化一個計數,每次有線程完成工作時計數減一,當計數為 0 時,釋放所有等待的線程。
  1. 消息傳遞(更高級、更安全的模式)
    這種模式解耦了線程,線程之間不直接共享內存,而是通過一個“中間人”(通常是隊列)來傳遞數據“消息”。生產者線程放入消息,消費者線程取出消息。
  • 如何進行:使用生產者/消費者模式。
  • .NET 提供的強大工具:System.Collections.Concurrent 命名空間下的線程安全集合。
    • BlockingCollection:一個提供了阻塞和邊界功能的線程安全集合。它是實現生產者/消費者模式的最佳工具。

      // 創建一個最多容納10個項目的阻塞集合
      BlockingCollection<string> _messageQueue = new BlockingCollection<string>(10);// 生產者線程
      void Producer()
      {while (true){string message = GenerateMessage();_messageQueue.Add(message); // 如果隊列滿了,Add 會阻塞生產者}_messageQueue.CompleteAdding(); // 通知消費者不會再生產了
      }// 消費者線程
      void Consumer()
      {// GetConsumingEnumerable() 會在沒有數據時阻塞消費者,并在 CompleteAdding() 且隊列空后自動結束foreach (var message in _messageQueue.GetConsumingEnumerable()){ProcessMessage(message);}
      }
      
    • ConcurrentQueue, ConcurrentStack, ConcurrentBag, ConcurrentDictionary<TKey, TValue>:這些是線程安全的集合,可以在不加鎖的情況下被多個線程同時讀寫,但它們本身不提供阻塞功能。

總結與最佳實踐

通信機制如何實現適用場景優點缺點
共享內存共享變量 + 鎖/同步原語高頻、簡單的數據共享性能高、直觀容易死鎖、難以編寫和維護
信號機制EventWaitHandle, Semaphore, Barrier線程間的協調、通知、同步目的明確,易于理解不直接傳遞數據,容易錯過信號
消息傳遞BlockingCollection< T > + 并發集合生產者/消費者、解耦復雜任務安全性高、解耦、易于擴展有一定的性能開銷(入隊/出隊)

現代 .NET 多線程編程的最佳實踐:

  1. 優先選擇消息傳遞模式:使用 BlockingCollection 或 Channel (.NET Core 3.0+) 可以極大地減少對鎖的依賴,從而避免死鎖等問題,代碼也更清晰。

  2. 避免共享狀態:如果可能,盡量設計無狀態的操作,讓每個線程只處理自己的數據。

  3. 使用高級抽象:優先使用 Task、Parallel 循環和 PLINQ,而不是手動管理 Thread 對象。它們底層使用線程池,效率更高。

  4. 善用異步編程:對于 I/O 密集型操作(如文件、網絡),使用 async/await 而不是創建阻塞線程,這樣可以釋放線程去處理其他請求,大大提高應用程序的吞吐量。

  5. 始終牢記線程安全:只要存在共享,第一反應就應該是“如何同步”。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/96060.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/96060.shtml
英文地址,請注明出處:http://en.pswp.cn/web/96060.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(JVM)四種垃圾回收算法

在 JVM 中&#xff0c;垃圾回收&#xff08;GC&#xff09;是核心機制之一。為了提升性能與內存利用率&#xff0c;JVM 采用了多種垃圾回收算法。本文總結了 四種常見的 GC 算法&#xff0c;并結合其優缺點與應用場景進行說明。1. 標記-清除&#xff08;Mark-Sweep&#xff09;…

論文閱讀:VGGT Visual Geometry Grounded Transformer

論文閱讀&#xff1a;VGGT: Visual Geometry Grounded Transformer 今天介紹一篇 CVPR 2025 的 best paper&#xff0c;這篇文章是牛津大學的 VGG 團隊的工作&#xff0c;主要圍繞著 3D 視覺中的各種任務&#xff0c;這篇文章提出了一種多任務統一的架構&#xff0c;實現一次輸…

python編程:一文掌握pypiserver的詳細使用

更多內容請見: python3案例和總結-專欄介紹和目錄 文章目錄 一、 pypiserver 概述 1.1 pypiserver是什么? 1.2 核心特性 1.3 典型應用場景 1.4 pypiserver優缺點 二、 安裝與基本使用 2.1 安裝 pypiserver 2.2 快速啟動(最簡模式) 2.3 使用私有服務器安裝包 2.4 向私有服務…

Git reset 回退版本

- 第 121 篇 - Date: 2025 - 09 - 06 Author: 鄭龍浩&#xff08;仟墨&#xff09; 文章目錄Git reset 回退版本1 介紹三種命令區別3 驗證三種的區別3 如果不小心git reset --hard將「工作區」和「暫存區」中的內容刪除&#xff0c;剛才的記錄找不到了&#xff0c;怎么辦呢&…

ARM 基礎(2)

ARM內核工作模式及其切換條件用戶模式(User Mode, usr) 權限最低&#xff0c;運行普通應用程序。只能通過異常被動切換到其他模式。快速中斷模式(FIQ Mode, fiq) 處理高速外設中斷&#xff0c;專用寄存器減少上下文保存時間&#xff0c;響應周期約4個時鐘周期。觸發條件為FIQ中…

Flutter 性能優化

Flutter 性能優化是一個系統性的工程&#xff0c;涉及多個層面。 一、性能分析工具&#xff08;Profiling Tools&#xff09; 在開始優化前&#xff0c;必須使用工具定位瓶頸。切忌盲目優化。 1. DevTools 性能視圖 DevTools 性能視圖 (Performance View) 作用&#xff1a;…

Spring事件監聽機制(三)

為了理解EvenListener注解的底層原理&#xff0c;我們可以自己實現一個類似的注解模擬實現。1.定義MyListener注解Target({ElementType.METHOD})Retention(RetentionPolicy.RUNTIME)public interface MyListener {}2.注解使用Componentstatic class SmsService {private static…

基于Springboot + vue3實現的小區物業管理系統

項目描述本系統包含管理員和用戶兩個角色。管理員角色&#xff1a;用戶管理&#xff1a;管理系統中所有用戶的信息&#xff0c;包括添加、刪除和修改用戶。房屋信息管理&#xff1a;管理房屋信息&#xff0c;包括新增、查看、修改和刪除房屋信息。車輛信息管理&#xff1a;管理…

交叉熵和KL散度

這個問題之前我也是傻傻分不清&#xff0c;決定整理一下&#xff0c;用更印象深刻的方式讓人記住。核心聯系&#xff1a;交叉熵 KL 散度 真實分布的熵 交叉熵作為 “絕對” 度量&#xff0c;會綜合真實分布的熵&#xff08;固有難度&#xff09;與預測誤差&#xff0c;直接體…

HTML 各種事件的使用說明書

HTML 各種事件的使用說明書 1. HTML 事件簡介 HTML事件是瀏覽器或用戶在網頁上執行的動作或發生的事情。當這些事件發生時&#xff0c;可以通過JavaScript來響應和處理這些事件&#xff0c;從而實現網頁的交互功能。事件處理是Web前端開發中實現動態交互的核心機制。 基本概…

Kafka面試精講 Day 10:事務機制與冪等性保證

【Kafka面試精講 Day 10】事務機制與冪等性保證 在分布式消息系統中&#xff0c;如何確保消息不丟失、不重復&#xff0c;是系統可靠性的核心挑戰。Kafka自0.11版本起引入了冪等性Producer和事務性消息機制&#xff0c;徹底解決了“至少一次”語義下可能產生的重復消息問題&am…

時序數據庫簡介和安裝

一、簡介1. 什么是時序數據庫&#xff1f;時序數據庫是專門用于存儲和處理時間序列數據的數據庫系統。時間序列數據是指按時間順序索引的一系列數據點。每個數據點都包含&#xff1a;一個時間戳&#xff1a;記錄數據產生的時間。一個或多個指標值&#xff1a;例如溫度、濕度、C…

comfyUI 暴露網絡restful http接口

https://zhuanlan.zhihu.com/p/686893291 暴露websocket接口。 打開開發者選項 如圖

linux系統address already in use問題解決

linux系統上某個端口被占用&#xff0c;如何解決&#xff1f;1.找到占用的進程編號&#xff1a;netstat -tulnp | grep :80002.強制殺死該進程kill -9 80603其他說明&#xff1a;1.查找占用端口的進程&#xff0c;可以用&#xff1a;lsof -i :8001 # 或者使用 netstat -tulnp |…

基于SpringBoot的家政保潔預約系統【計算機畢業設計選題 計算機畢業設計項目 計算機畢業論文題目推薦】

&#x1f34a;作者&#xff1a;計算機編程-吉哥 &#x1f34a;簡介&#xff1a;專業從事JavaWeb程序開發&#xff0c;微信小程序開發&#xff0c;定制化項目、 源碼、代碼講解、文檔撰寫、ppt制作。做自己喜歡的事&#xff0c;生活就是快樂的。 &#x1f34a;心愿&#xff1a;點…

【Linux系統】 4. 權限(一)

一. shell 命令及運行原理基本理解1&#xff09;廣義理解的操作系統包括&#xff1a;操作系統內核、外殼程序&#xff08;shell命令行、圖形化界面&#xff09;、必要的軟件。2&#xff09;狹義的操作系統&#xff1a;操作系統內核。3&#xff09;在用戶和內核之間有一個外殼程…

6.python——字符串

python中用’ 和" "創建字符串 python的子字符串截取用[]取字符串拼接可以直接用相加。 python三引號允許一個字符串跨多行&#xff0c;其中無需進行轉義&#xff08;所見即所得&#xff09;。 當你需要一塊HTML或者SQL時&#xff0c;這時用字符串組合&#xff0c;特…

足球數據API接口的技術特性與應用價值分析

一、接口概述現代足球數據接口是基于RESTful架構的數據服務&#xff0c;通過標準化方式提供賽事相關信息。這類接口通常采用JSON格式傳輸數據&#xff0c;支持跨平臺調用&#xff0c;為開發者提供結構化的足球賽事數據。二、數據覆蓋范圍主流足球數據接口通常包含以下數據類型&…

<android>反編譯魔改安卓系統應用并替換

我們知道安卓系統基于穩定性、維護便利、性能優化等原因并未對原生系統apk進行混淆加密處理&#xff0c;由此就方便了我們反編譯替換原生應用。 首先我們設備需要是root后的&#xff0c;我是使用的是小米5&#xff0c;刷的24.3版本的面具。首先我們需要取系統apk&#xff0c;這…

【Qt】項目的創建 and 各個控件的使用

一、項目的創建&#x1f50d;然后點擊新建項目。&#x1f4d6;注意&#xff1a;路徑不要帶有中文&#xff0c;不然運行不了代碼。&#x1f4d6;qmake是一個構建工具&#xff0c;在 Qt 寫的程序&#xff0c;設計的到一系列的 "元編程" 技術&#xff0c;什么是元編程技…