一般在會議、教學或培訓活動中,我們都會選擇PPT文檔來進行內容展示。與PDF文檔相比,PPT文檔具有較強的可編輯性,可以隨時增刪元素,並且還可以設置豐富多樣的動畫效果來吸引觀眾註意。那麼如何通過C#將PDF文檔轉為PPT文檔呢?本文將教大家僅使用3行代碼就實現這一功能。 PDF轉PPT所需工具: S ...
.NET中至少有6種定時器,每一種定時器都有它的用途和特點。根據定時器的應用場景,可以分為UI相關的定時器和UI無關的定時器。本文將簡單介紹這6種定時器的基本用法和特點。
UI定時器
.NET中的UI定時器主要是WinForm、WPF以及WebForm中的定時器。分別為:
System.Windows.Forms.Timer
System.Windows.Threading.DispatcherTimer
System.Web.UI.Timer
通常情況下,WinForm、WPF中的定時器是在UI線程上執行回調函數,因此可以直接訪問UI元素。由於WinForm、WPF支持單線程單元模型(Single-Thread Apartment,STA),定時器間隔事件是在UI線程上觸發,因此,不用擔心線程安全問題。
System.Web.UI.Timer
是通過Javascript定時器和服務端非同步回調實現,也是單線程的。
請註意,這裡說的是通常情況,後邊介紹
System.Windows.Threading.DispatcherTimer
時會提到在非UI線程創建DispatcherTimer
時也無法直接訪問UI元素。
System.Windows.Forms.Timer
System.Windows.Forms.Timer
針對WinForm應用進行了優化,是只能在WinForm上使用的定時器。這個定時器是針對單線程環境設計的,是在UI線程上處理定時任務。
它要求用戶代碼有可用的UI消息泵,定時任務須在UI線程上運行,或者跨線程通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運行。其優點是使用簡單,只需通過給Interval
屬性賦值來設置時間間隔,並註冊Tick
事件處理定時任務。其缺點是精度不高,精度為55毫秒,也就是Interval
賦值小於55時,也是55毫秒觸發一次定時任務。
public partial class TimerFrom : Form
{
private System.Windows.Forms.Timer digitalClock;
private void TimerFrom_Load(object sender, EventArgs e)
{
digitalClock = new System.Windows.Forms.Timer();//創建定時器
digitalClock.Tick += new EventHandler(HandleTime);//註冊定時任務事件
digitalClock.Interval = 1000;//設置時間間隔
digitalClock.Enabled = true;
digitalClock.Start(); //開啟定時器
}
public void HandleTime(Object myObject, EventArgs myEventArgs)
{
labelClock.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
private void frmTimerDemo_FormClosed(object sender, FormClosedEventArgs e)
{
digitalClock.Stop();//停止定時器
digitalClock.Dispose();
}
}
System.Windows.Threading.DispatcherTimer
System.Windows.Threading.DispatcherTimer
是WPF中的定時器,它是基於Dispatcher
對象的(並不是基於UI線程的)。DispatcherTimer
的定時任務是像其他操作一樣放在Dispatcher
隊列上,其執行操作時間依賴於隊列中其他任務及其優先順序,因此,DispatcherTimer
不保證在時間間隔發生時準確執行,只保證不會在時間間隔發生前執行。
Dispatcher
為特定線程維護工作項(操作)的優先順序隊列,線上程上創建Dispatcher
對象時,它成為唯一可以關聯該線程的Dispatcher
對象,WPF中,DispatcherObject
只能被與之關聯的Dispatcher
對象訪問,也就是非UI線程中無法直接訪問UI元素(WPF中的UI元素都是派生自DispatcherObject
)
此外,DispatcherTimer
不像System.Windows.Forms.Timer
那樣只在UI線程上創建才能觸發Tick
事件,它在非UI線程下創建也可以觸發Tick
事件,此時訪問UI元素也需要通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運行。其優點也是簡單易用,適合在UI線程上執行任務或觸發事件,缺點是精度不准確,可能存在延遲。
private void Dt_Tick(object sender, EventArgs e)
{
Dispatcher.BeginInvoke((Action)delegate ()
{
text1.Text = DateTime.Now.ToString();
});
Console.WriteLine(DateTime.Now.ToString());
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>{
DispatcherTimer dt = new DispatcherTimer();
dt.Tick += Dt_Tick;
dt.Interval = TimeSpan.FromSeconds(1);
dt.Start();
Dispatcher.Run();
});
}
上述代碼中,DispatcherTimer
是非UI線程中創建,定時任務中訪問UI元素text1,需要通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運行,而Console.WriteLine
則可以直接運行。
System.Web.UI.Timer
System.Web.UI.Timer
是僅適用於.NET Framework
的ASP.NET
組件。通過Javascript定時器和服務端非同步回調實現。每次觸發定時器時,只能執行一個非同步回調方法,而其他的非同步回調方法需要等待前一個非同步回調方法執行完畢後才能執行。這樣可以保證在任意時刻只有一個非同步回調方法在執行,避免了多線程併發執行的問題。
UI無關定時器
從 .NET 6開始,UI無關定時器有三個:
System.Threading.Timer
System.Timers.Timer
System.Threading.PeriodicTimer
(.NET 6+)
System.Threading.Timer
System.Threading.Timer
是最基礎輕量的定時器,它將定期線上程池線程上執行單個回調方法。在創建定時器對象時必須指定回調方法,並且後續不能修改,同時也可以指定定時器回調開始執行的時間以及時間間隔。定時器創建後可以通過Change
方法修改回調開始執行的時間以及時間間隔。該定時器的優點是輕量,精度相對較高,與Windows操作系統時鐘精度一致,大約15毫秒。但因為是基於線程池的,所以在任務執行時間較長或者線程池過載時,會出現延遲。其缺點是使用不太方便,定時器創建後無法修改回調方法。
var stateTimer = new
var autoEvent = new AutoResetEvent(false);
Timer(CheckStatus, autoEvent, 1000,250);
private int invokeCount=0;
public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine("{0} Checking status {1,2}.",DateTime.Now.ToString("h:mm:ss.fff"),(++invokeCount).ToString());
if(invokeCount == 10)
{
invokeCount = 0;
autoEvent.Set();
}
}
System.Timers.Timer
System.Timers.Timer
在內部使用System.Threading.Timer
,並公開了更多的屬性,如AutoReset
, Enabled
或SynchronizingObject
,這些屬性允許配置回調的執行方式。此外,Tick事件允許註冊多個處理程式。因此,一個定時器可以觸發多個處理程式。還可以在計時器啟動後更改處理程式。與System.Threading.Timer
相似,其優點也是精度相對較高,與Windows操作系統時鐘精度一致,大約15毫秒。因為預設(或者SynchronizingObject=null
時)是基於線程池的,所以在任務執行時間較長或者線程池過載時,會出現延遲。但使用要更簡便一些。
public partial class TimerFrom : Form
{
private System.Timers.Timer timer;
private void TimerFrom_Load(object sender, EventArgs e)
{
// 支持註冊多個處理程式
timer.Elapsed += (sender, e) => { label1.Text = DateTime.Now.ToLongTimeString(); };
timer.Elapsed += (sender, e) => { Console.WriteLine(DateTime.Now.ToLongTimeString()); };
//自定義回調執行的方式(指定對象所在的線程),SynchronizingObject=null時線上程池上執行
timer.SynchronizingObject = this;
timer.AutoReset = true;
timer.Start();
}
}
本例中將SynchronizingObject
屬性設置為Form
對象,因此Elapsed
的處理程式在UI線程上執行,可以直接修改 label1.Text
,如果SynchronizingObject
屬性為null
,處理程式則是線上程池線程上執行,修改 label1.Text
時需要通過Invoke
或者BeginInvoke
封送(marshal)到UI線程上運行。
System.Threading.PeriodicTimer
System.Threading.PeriodicTimer
是 .NET 6中引入的定時器。它能方便地使用非同步方式,它沒有Tick
事件,而是提供WaitForNextTickAsync
方法處理定時任務。通常是使用While
迴圈結合CancellationToken
一起使用。和CancellationToken
一起用的時候需要註意,如果CancellationToken
被取消的時候會拋出一個OperationCanceledException
需要考慮自己處理異常。相比之前的定時器來說,有下麵幾個特點:[1]
- 沒有
callback
來綁定事件; - 不會發生重入,只允許有一個消費者,不允許同一個
PeriodicTimer
在不同的地方同時WaitForNextTickAsync
,不需要自己做排他鎖來實現不能重入; - 非同步化。之前的 timer 的 callback 都是同步的,使用新 timer 可以使用非同步方法,避免了編寫 Sync over Async 代碼;
- Dispose 之後,實例就無法使用,並且 WaitForNextTickAsync 始終返回 false。
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
using (var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)))
{
try
{
while (await timer.WaitForNextTickAsync(cts.Token))
{
await Task.Delay(3000);
Console.WriteLine($"ThreadId is {Thread.CurrentThread.ManagedThreadId} --- Time is {DateTime.Now:HH:mm:ss}");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation cancelled");
}
}
小結
我們在開發過程中遇到的坑往往不是技術本身的坑,而是我們濫用沒有掌握的技術導致的,在有多種技術方案可選的時候,通常只關註技術的優點,忽略了技術適用場景及其局限性。.NET中幾種定時器各自都有其適用場景和不足,但都不支持高精度計時。瞭解這些有助於我們在開發過程中選擇合適定時器,避免遇到問題後被動地替換解決方案。