FreeRTOS教程9 軟體定時器

来源:https://www.cnblogs.com/lc-guo/p/18073051
-Advertisement-
Play Games

本文主要學習 FreeRTOS 軟體定時器的相關知識,包括軟體定時器回調函數、屬性、狀態、運行原理和常見 API 函數等知識 ...


1、準備材料

正點原子stm32f407探索者開發板V2.4

STM32CubeMX軟體(Version 6.10.0

Keil µVision5 IDE(MDK-Arm

野火DAP模擬器

XCOM V2.6串口助手

2、學習目標

本文主要學習 FreeRTOS 軟體定時器的相關知識,包括軟體定時器回調函數、屬性、狀態、運行原理和常見 API 函數等知識

3、前提知識

3.1、軟體定時器回調函數

軟體定時器的回調函數是一個返回值為 void 類型,並且只有軟體定時器句柄一個參數的 C 語言函數,其函數的具體原型如下所述

/**
  * @brief  軟體定時器回調函數
  * @param  xTimer:軟體定時器句柄
  * @retval None
  */
void ATimerCallback(TimerHandle_t xTimer)
{
	/* do something */
}

軟體定時器回調函數會在定時器設定的時間到期時在 RTOS 守護進程任務中被執行,軟體定時器回調函數從頭到尾執行,並以正常方式退出

需要讀者註意的是軟體定時器的回調函數應儘可能簡短,並且在該函數體內不能調用任何會使任務進入阻塞狀態的 API 函數,但是如果設置調用函數的 xTicksToWait 參數為 0 ,則可以調用如 xQueueReceive() 等 API 函數

3.2、軟體定時器屬性和狀態

3.2.1、周期

這個屬性比較好理解,軟體定時器的周期指的是 從軟體定時器啟動到軟體定時器回調函數執行之間的時間,該屬性是定時器不可或缺的重要屬性

3.2.2、分類

軟體定時器根據行為的不同分為了 單次定時器(One-shot timers) 和 周期定時器(Auto-reload timers) 兩種類型,如下圖展示了兩種不同類型軟體定時器的行為差異 (註釋1)

3.2.3、狀態

根據定時器是否正在運行可以將定時器分為 運行狀態(Running) 和 休眠狀態(Dormant) 兩種不同狀態,如下圖所示展示了單次定時器和周期定時器在兩種不同狀態之間的轉換過程

從圖上可以看出以下幾點內容

  1. 不管是單次定時器還是周期定時器,在定時器創建成功之後都處於休眠狀態,一旦調用啟動、複位或改變定時器周期的 API 函數就會使定時器從休眠狀態轉移到運行狀態;
  2. 單次定時器定時時間到期之後執行一次回調函數就會自動轉換為休眠狀態,而周期定時器會一直處於運行狀態;
  3. 當對處於運行狀態的定時器調用停止 API 函數時,不管是哪種定時器都會轉變為休眠狀態

定時器的狀態可以通過 xTimerlsTimerActive() API 函數查詢,該函數具體聲明如下所述

/**
  * @brief  查詢軟體定時器是否處於運行或休眠狀態
  * @param  xTimer:要查詢的定時器句柄
  * @retval 如果定時器處於休眠狀態則返回pdFALSE,如果定時器處於運行狀態則返回pdTRUE
  */
BaseType_t xTimerIsTimerActive(TimerHandle_t xTimer);

3.3、軟體定時器運行原理

3.3.1、RTOS 守護進程任務

首先讀者應該知道的一點是所有軟體定時器的回調函數都在同一個 RTOS 守護進程任務的上下文中執行,這個 RTOS 守護進程任務和空閑任務一樣,在調度器啟動的時候會被自動創建, RTOS 守護進程任務的優先順序和堆棧大小分別由 configTIMER_TASK_PRIORITY configTIMER_TASK_STACK_DEPTH 兩個參數設置(可在 STM32CubeMX 軟體中配置)

”3.1、軟體定時器回調函數“ 小節提到在回調函數中不能使用會使任務進入阻塞狀態的 API 函數,這是因為調用會使任務進入阻塞狀態的 API 函數會使 RTOS 守護進程任務進入阻塞狀態,這是不被允許的

3.3.2、定時器命令隊列

上面提到的軟體定時器的啟動、複位、改變定時器周期和停止等操作的 API 函數只是將控制定時器的命令從調用任務發送到稱為 “定時器命令隊列” 的隊列上,然後由 RTOS 守護進程任務從定時器命令隊列中取出命令對定時器實際操作

定時器命令隊列是 FreeRTOS 里的一個標準隊列,其也是在調度程式啟動時被自動創建的,定時器命令隊列的長度可以由 configTIMER_QUEUE LENGTH 參數設置

如下圖所示為軟體定時器 API 函數使用定時器命令隊列與 RTOS 守護程式任務進行通信的示意圖

3.3.3、守護進程任務調度

守護進程任務是一個 FreeRTOS 任務,所以其任務調度會遵循和其他任務一樣的調度規則,當守護進程任務是能夠運行的最高優先順序任務時,它將會處理定時器隊列中的命令或執行定時器的回調函數

守護進程任務的優先順序在 STM32CubeMX 中預設為 2 ,當守護進程任務的優先順序低於調用 xTimerStart() 等 API 函數的任務的優先順序時,其會在任務結束之後輪到守護進程任務執行時對 “開始定時器” 命令進行處理,具體如下圖所示

當守護進程任務的優先順序高於調用 xTimerStart() 等 API 函數的任務的優先順序時,一旦任務調用 xTimerStart() 等 API 函數將命令寫入定時器命令隊列,守護進程任務便可以搶占該任務,立即處理寫入定時器命令隊列的命令,處理完畢之後進入阻塞狀態,處理器返回原任務繼續執行,具體如下圖所示

3.4、創建、啟動軟體定時器

同樣,根據 FreeRTOS API 的慣例,創建軟體定時器仍然提供了動態記憶體創建和靜態記憶體創建兩個不同的 API 函數,軟體定時器可以在調度程式運行之前創建,也可以在調度程式啟動後從任務創建,如下所示為兩個 API 函數聲明

/**
  * @brief  動態分配記憶體創建軟體定時器
  * @param  pcTimerName:定時器的描述性名稱,輔助調試用
  * @param  xTimerPeriod:定時器的周期,參考 “3.2.1、周期” 小節
  * @param  uxAutoReload:pdTRUE表示周期軟體定時器,pdFASLE表示單次軟體定時器
  * @param  pvTimerID:定時器ID
  * @param  pxCallbackFunction:定時器回調函數指針,參考 “3.1、軟體定時器回調函數” 小節
  * @retval 創建成功則返回創建的定時器的句柄,失敗則返回NULL
  */
TimerHandle_t xTimerCreate(const char * const pcTimerName,
						   const TickType_t xTimerPeriod,
						   const UBaseType_t uxAutoReload,
						   void * const pvTimerID,
						   TimerCallbackFunction_t pxCallbackFunction);

/**
  * @brief  動態分配記憶體創建軟體定時器
  * @param  pcTimerName:定時器的描述性名稱,輔助調試用
  * @param  xTimerPeriod:定時器的周期,參考 “3.2.1、周期” 小節
  * @param  uxAutoReload:pdTRUE表示周期軟體定時器,pdFASLE表示單次軟體定時器
  * @param  pvTimerID:定時器ID
  * @param  pxCallbackFunction:定時器回調函數指針,參考 “3.1、軟體定時器回調函數” 小節
  * @param  pxTimerBuffer:指向StaticTimer_t類型的變數,然後用該變數保存定時器的狀態
  * @retval 創建成功則返回創建的定時器的句柄,失敗則返回NULL
  */
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
								  const TickType_t xTimerPeriod,
								  const UBaseType_t uxAutoReload,
								  void * const pvTimerID,
								  TimerCallbackFunction_t pxCallbackFunction
								  StaticTimer_t *pxTimerBuffer);

創建完的軟體定時器處於休眠狀態,需要調用啟動定時器或其他 API 函數才會進入運行狀態,xTimerStart() 可以在調度程式啟動之前調用,但是完成此操作後,軟體定時器直到調度程式啟動的時間才會真正啟動,啟動定時器的 API 函數如下所述

/**
  * @brief  啟動定時器
  * @param  xTimer:要操作的定時器句柄
  * @param  xBlockTime:參考 “3.4.1、xTicksToWait 參數” 小節
  * @retval 參考 “3.4.2、函數返回值” 小節
  */
BaseType_t xTimerStart(TimerHandle_t xTimer,
					   TickType_t xTicksToWait);

/**
  * @brief  啟動定時器的中斷安全版本
  * @param  xTimer:要操作的定時器句柄
  * @param  pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
  * @retval 參考 “3.4.2、函數返回值” 小節
  */
BaseType_t xTimerStartFromISR(TimerHandle_t xTimer,
							  BaseType_t *pxHigherPriorityTaskWoken);

3.4.1、xTicksToWait 參數

xTimerStart() 使用定時器命令隊列向守護進程任務發送 “啟動定時器” 命令, xTicksToWait 指定調用任務應保持在阻塞狀態以等待定時器命令隊列上的空間變得可用的最長時間(如果隊列已滿),該參數需要註意以下幾點

  1. 如果 xTicksToWait 為零且定時器命令隊列已滿,xTimerStart() 將立即返回,該參數以滴答定時器時間刻度為單位,可以使用巨集 pdMS_TO_TICKS() 將以毫秒為單位的時間轉換為以刻度為單位的時間,例如 pdMS_TO_TICKS(50) 表示阻塞 50ms

  2. 如果在 FreeRTOSConfig.h 中將 INCLUDE_vTaskSuspend 設置為 1,則將 xTicksToWait 設置為 portMAX_DELAY 將導致調用任務無限期地保持在阻塞狀態(沒有超時),以等待定時器命令隊列中的空間變得可用

  3. 如果在啟動調度程式之前調用 xTimerStart(),則 xTicksToWait 的值將被忽略,並且 xTimerStart() 的行為就像 xTicksToWait 已設置為零一樣

3.4.2、xTimerStart() 函數返回值

有兩種可能的返回值,分別為 pdPASS 和 pdFALSE ,具體如下所述

① 僅當 “啟動定時器” 命令成功發送到定時器命令隊列時,才會返回 pdPASS

  1. 如果守護程式任務的優先順序高於調用 xTimerStart() 的任務的優先順序,則調度程式將確保在 xTimerStart() 返回之前處理啟動命令。這是因為一旦定時器命令隊列中有數據,守護任務就會搶占調用 xTimerStart() 的任務,從而總是保證將命令成功發送到定時器命令隊列
  2. 如果指定了阻塞時間(xTicksToWait 不為零),則調用任務可能會被置於阻塞狀態,以等待定時器命令隊列中的空間在函數返回之前變得可用,只要在阻塞時間到期之前命令已成功寫入定時器命令隊列,就可以返回 pdPASS

② 如果由於隊列已滿或超過阻塞時間等原因無法將 “啟動定時器” 命令寫入定時器命令隊列,則將返回 pdFALSE

  1. 如果指定了阻塞時間(xTicksToWait 不為零),則調用任務將被置於阻塞狀態以等待守護進程任務在定時器命令隊列中騰出空間,但是指定的阻塞時間在等待定時器命令隊列中騰出空間之前已過期,所以返回 pdFALSE

3.6、軟體定時器 ID

每個軟體定時器都有一個 ID ,它是一個標簽值,應用程式編寫者可以將其用於任何目的, ID 被存儲在空指針中,因此可以直接存儲整數值,指向任何其他對象,或用作函數指針

創建軟體定時器時會為 ID 分配一個初始值,之後可以使用 vTimerSetTimerID() API 函數更新 ID,並且可以使用 pvTimerGetTimerID() API 函數查詢 ID ,這兩個 API 函數具體如下所示

/**
  * @brief  設置定時器ID值
  * @param  xTimer:要操作的定時器句柄
  * @param  pvNewID:想要設置軟體定時器的新ID值
  * @retval None
  */
void vTimerSetTimerID(TimerHandle_t xTimer, void *pvNewID);

/**
  * @brief  獲取定時器ID值
  * @param  xTimer:要操作的定時器句柄
  * @retval 正在查詢的軟體定時器ID
  */
void *pvTimerGetTimerID(TimerHandle_t xTimer);

註意:與其他軟體定時器 API 函數不同,vTimerSetTimerID() 和 pvTimerGetTimerID() 直接訪問軟體定時器,它們不向定時器命令隊列發送命令

如果創建了多個軟體定時器,並且所有軟體定時器均使用了同一個回調函數,則可以給軟體定時器設置不同的 ID 值,然後在回調函數中通過 ID 值判斷軟體定時器觸發的來源

3.7、改變軟體定時器周期

創建軟體定時器時就會為定時器周期設置初始值,後續也可以使用 xTimerChangePeriod() 函數動態更改軟體定時器的周期,該函數具體聲明如下所示

/**
  * @brief  改變軟體定時器的周期
  * @param  xTimer:要操作的定時器句柄
  * @param  xNewPeriod:軟體定時器的新周期,以刻度為單位指定
  * @param  xBlockTime:參考 “3.4.1、xTicksToWait 參數” 小節
  * @retval 參考 “3.4.2、xTimerStart() 函數返回值” 小節
  */
 BaseType_t xTimerChangePeriod(TimerHandle_t xTimer,
							   TickType_t xNewPeriod,
							   TickType_t xBlockTime);

/**
  * @brief  改變軟體定時器周期的中斷安全版本
  * @param  xTimer:要操作的定時器句柄
  * @param  xNewPeriod:軟體定時器的新周期,以刻度為單位指定
  * @param  pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
  * @retval 參考 “3.4.2、xTimerStart() 函數返回值” 小節
  */
 BaseType_t xTimerChangePeriodFromISR(TimerHandle_t xTimer,
									  TickType_t xNewPeriod,
									  BaseType_t *pxHigherPriorityTaskWoken);

如果 xTimerChangePeriod() 用於更改已運行的定時器的周期,則定時器將使用新的周期值重新計算其到期時間,重新計算的到期時間是相對於調用 xTimerChangePeriod() 的時間,而不是相對於定時器最初啟動的時間

如果使用 xTimerChangePeriod() 更改處於休眠狀態(未運行的定時器)的定時器的周期,則定時器將計算到期時間,並轉換到運行狀態(定時器將開始運行)

另外如果希望查詢一個定時器的定時周期,可以通過 xTimerGetPeriod() API 函數查詢,具體函數聲明如下所述

/**
  * @brief  查詢一個軟體定時器的周期
  * @param  xTimer:要查詢的定時器句柄
  * @retval 返回一個軟體定時器的周期
  */
TickType_t xTimerGetPeriod(TimerHandle_t xTimer);

3.8、重置軟體定時器

重置軟體定時器是指重新啟動定時器,定時器的到期時間將根據重置定時器的時間重新計算,而不是相對於定時器最初啟動的時間,如下圖對此進行了演示,其中顯示了一個定時器,該定時器啟動的周期為 6,然後重置兩次,最後到期並執行其回調函數

FreeRTOS中使用 xTimerReset() API 函數重置軟體定時器,除此之外還可用於啟動處於休眠狀態的定時,該函數具體聲明如下所述

/**
  * @brief  重置軟體定時器
  * @param  xTimer:要操作的定時器句柄
  * @param  xBlockTime:參考 “3.4.1、xTicksToWait 參數” 小節
  * @retval 參考 “3.4.2、xTimerStart() 函數返回值” 小節
  */
BaseType_t xTimerReset(TimerHandle_t xTimer,
					   TickType_t xBlockTime);

/**
  * @brief  重置軟體定時器的中斷安全版本
  * @param  xTimer:要操作的定時器句柄
  * @param  pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
  * @retval 參考 “3.4.2、xTimerStart() 函數返回值” 小節
  */
BaseType_t xTimerResetFromISR(TimerHandle_t xTimer,
							  BaseType_t *pxHigherPriorityTaskWoken);

3.8、停止、刪除軟體定時器

/**
  * @brief  停止軟體定時器
  * @param  xTimer:要操作的定時器句柄
  * @param  xBlockTime:參考 “3.4.1、xTicksToWait 參數” 小節
  * @retval 參考 “3.4.2、xTimerStart() 函數返回值” 小節
  */
BaseType_t xTimerStop(TimerHandle_t xTimer,
					  TickType_t xBlockTime);

/**
  * @brief  刪除軟體定時器
  * @param  xTimer:要操作的定時器句柄
  * @param  xBlockTime:參考 “3.4.1、xTicksToWait 參數” 小節
  * @retval 參考 “3.4.2、xTimerStart() 函數返回值” 小節
  */
BaseType_t xTimerDelete(TimerHandle_t xTimer,
						TickType_t xBlockTime);

/**
  * @brief  停止軟體定時器的中斷安全版本
  * @param  xTimer:要操作的定時器句柄
  * @param  pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
  * @retval 參考 “3.4.2、xTimerStart() 函數返回值” 小節
  */
BaseType_t xTimerStopFromISR(TimerHandle_t xTimer,
							 BaseType_t *pxHigherPriorityTaskWoken);

3.9、其他 API 函數

/**
  * @brief  將軟體定時器的“模式”更新為 自動重新載入定時器 或 一次性定時器 
  * @param  xTimer:要操作的定時器句柄
  * @param  uxAutoReload:設置為pdTRUE則將定時器設置為周期軟體定時器,設置為pdFASLE則將定時器設置為單次軟體定時器
  * @retval None
  */
void vTimerSetReloadMode(TimerHandle_t xTimer,
						 const UBaseType_t uxAutoReload);

/**
  * @brief  查詢軟體定時器是 單次定時器 還是 周期定時器
  * @param  xTimer:要查詢的定時器句柄
  * @retval 如果為周期軟體定時器則返回pdTRUE,否則返回pdFALSE
  */
BaseType_t xTimerGetReloadMode(TimerHandle_t xTimer);

/**
  * @brief  查詢軟體定時器到期的時間
  * @param  xTimer:要查詢的定時器句柄
  * @retval 如果要查詢的定時器處於活動狀態則返回定時器下一次到期的時間,否則未定義返回值
  */
TickType_t xTimerGetExpiryTime(TimerHandle_t xTimer);

4、實驗一:軟體定時器的應用

4.1、實驗目標

  1. 創建一個周期軟體定時器 TimerPeriodic 和一個單次軟體定時器 TimerOnce
  2. 創建一個按鍵掃描任務 Task_KeyScan,根據不同按鍵實現不同響應
  3. 當按鍵 WK_UP 按下時,設置周期定時器以 500ms 周期執行;當按鍵 KEY2 按下時,設置單次定時器以 1s 周期執行一次;當按鍵 KEY1 按下時,對周期定時器進行複位操作;當按鍵 KEY0 按下時,停止 TimerPeriodic 周期定時器

4.2、CubeMX相關配置

首先讀者應按照 "FreeRTOS教程1 基礎知識" 章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求

本實驗需要初始化 USART1 作為輸出信息渠道,具體配置步驟請閱讀“STM32CubeMX教程9 USART/UART 非同步通信”,如下圖所示

本實驗需要初始化開發板上 WK_UP、KEY2、KEY1 和 KEY0 用戶按鍵做普通輸入,具體配置步驟請閱讀“STM32CubeMX教程3 GPIO輸入 - 按鍵響應”,註意雖開發板不同但配置原理一致,如下圖所示

單擊 Middleware and Software Packs/FREERTOS ,在 Configuration 中單擊 Tasks and Queues 選項卡,雙擊預設任務修改其參數,如下所示

單擊 Timers and Semaphores ,在 Timers 中創建周期、單次兩個軟體定時器,如下所示

配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可

4.3、添加其他必要代碼

按照 “STM32CubeMX教程9 USART/UART 非同步通信” 實驗 “6、串口printf重定向” 小節增加串口 printf 重定向代碼,具體不再贅述

首先應該在 freertos.c 中添加軟體定時器的頭文件和使用到的 printf 的頭文件,如下所述

#include "timers.h"
#include "stdio.h"

然後實現按鍵掃描任務函數體,當按鍵 WK_UP 按下時啟動周期軟體定時器,當按鍵 KEY2 按下時啟動單次軟體定時器,當按鍵 KEY1 按下時對周期軟體定時器進行複位操作,當按鍵 KEY0 按下時停止周期定時器,具體如下所述

void AppTask_KeyScan(void *argument)
{
	/* USER CODE BEGIN AppTask_KeyScan */
	uint8_t key_value = 0;
	/* Infinite loop */
	for(;;)
	{
		key_value = 0;
		//按鍵WK_UP按下
		if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == GPIO_PIN_SET)
			key_value = 4;
		//按鍵KEY2按下
		if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
			key_value = 3;
		//按鍵KEY1按下
		if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
			key_value = 2;
		//按鍵KEY0按下
		if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
			key_value = 1;
		
		if(key_value != 0)
		{
			if(key_value == 4)
			{
				if(xTimerChangePeriod(TimerPeriodicHandle, 500, pdMS_TO_TICKS(500)) == pdTRUE)
				{
					printf("\r\nWK_UP PRESSED, TimerPeriodic Start!\r\n\r\n");
				}
			}
			if(key_value == 3)
			{
				if(xTimerChangePeriod(TimerOnceHandle, 1000, pdMS_TO_TICKS(500)) == pdTRUE)
				{
					printf("\r\nKEY2 PRESSED, TimerOnce Start!\r\n\r\n");
				}
			}
			else if(key_value == 2)
			{
				if(xTimerReset(TimerPeriodicHandle, pdMS_TO_TICKS(500)) == pdTRUE)
				{
					printf("\r\nKEY1 PRESSED, TimerPeriodic Reset!\r\n\r\n");
				}
			}
			else if(key_value == 1)
			{
				if(xTimerStop(TimerPeriodicHandle, pdMS_TO_TICKS(500)) == pdTRUE)
				{
					printf("\r\nKEY0 PRESSED, TimerPeriod Stop!\r\n\r\n");
				}
			}
			//有按鍵按下就進行按鍵消抖
			osDelay(300);
		}
		else
			osDelay(10);
	}
	/* USER CODE END AppTask_KeyScan */
}

最後實現單次/周期軟體定時器的兩個回調函數即可,回調函數內不做任何具體操作,僅通過串口輸出提示信息,如下所述

/* appTimerPeriodic function */
void appTimerPeriodic(void *argument)
{
  /* USER CODE BEGIN appTimerPeriodic */
	printf("Into appTimerPeriodic Function\r\n");
  /* USER CODE END appTimerPeriodic */
}

/* appTimerOnce function */
void appTimerOnce(void *argument)
{
  /* USER CODE BEGIN appTimerOnce */
	printf("Into appTimerOnce Function\r\n");
  /* USER CODE END appTimerOnce */
}

4.4、燒錄驗證

燒錄程式,打開串口助手後無任何信息輸出,當按下開發板上的 WK_UP 按鍵之後,會啟動以 500ms 為周期的周期軟體定時器,此時周期軟體定時器的回調函數會周期得到執行;當按下開發板上的 KEY2 按鍵之後,會啟動 1s 為周期的單次軟體定時器,此時單次軟體定時器的回調函數會得到執行,並且只執行了一次就停止了執行;當按下開發板上的 KEY1 按鍵時,會複位周期定時器;當按下開發板上的 KEY0 按鍵時,會停止周期定時器,整個過程串口的輸出信息如下圖所示

5、註釋詳解

註釋1:圖片來源於 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

參考資料

STM32Cube高效開發教程(基礎篇)

Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Nuget包 Microsoft.Extensions.Telemetry.Abstractions 包含的新的日誌記錄source generator,它支持使用[LogProperties]將整個對象作為State與日誌一起記錄。 我將展示一種方法來控制如何使用[LogProperties]對象 ...
  • 概述:以上內容詳細介紹了在C#中如何從另一個線程更新GUI,包括基礎功能和高級功能。對於WinForms,使用`Control.Invoke`;對於WPF,使用`Dispatcher.Invoke`。高級功能使用`SynchronizationContext`實現線程間通信,確保清晰、可讀性高的代碼 ...
  • using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; namespace OOP { pub ...
  • 本章將和大家分享如何通過 Elasticsearch 實現自動補全查詢功能。 一、自動補全-安裝拼音分詞器 1、自動補全需求說明 當用戶在搜索框輸入字元時,我們應該提示出與該字元有關的搜索項,如圖: 2、使用拼音分詞 要實現根據字母做補全,就必須對文檔按照拼音分詞。在 GitHub 上恰好有 Ela ...
  • 您是否曾經訪問過一個網站,它需要很長時間載入,最終你敲擊 F5 重新載入頁面。 即使用戶刷新了瀏覽器取消了原始請求,而對於伺服器來說,API也不會知道它正在計算的值將在結束時被丟棄,刷新五次,伺服器將觸發 5 個請求。 為瞭解決這個問題,ASP.NET Core 為 Web 伺服器提供了一種機制,就 ...
  • 概述:在WPF中使用`WpfAnimatedGif`庫展示GIF動畫,首先確保全裝了該庫。通過XAML設置Image控制項,指定GIF路徑,然後在代碼中使用庫提供的方法實現動畫控制。這簡化了在WPF應用中處理GIF圖的過程,提供了方便的介面來管理動畫播放和暫停。 當使用 WpfAnimatedGif  ...
  • 概述:這個WPF項目通過XAML繪製汽車動態速度表盤,實現了0-300的速度刻度,包括數字、指針,並通過定時器模擬速度變化,展示了動態效果。詳細實現包括界面設計、刻度繪製、指針角度計算等,通過C#代碼與XAML文件結合完成。 新建 WPF 項目: 在 Visual Studio 中創建一個新的 WP ...
  • Next Terminal —— 一個簡單好用安全的開源交互審計系統,支持 RDP、SSH、VNC、Telnet、Kubernetes 協議。 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...