[CLK Framework] CLK.Threading.PortableTimer - 跨平台的Timer类别
问题情景
开发应用程式的时候,免不了需要加入一些定时执行的设计,例如说:定时更新画面资料、定时检查资料库内容、定时检查通讯是否断线...等等。而.NET Framework也很贴心的提供三种不同的Timer类别,用来帮助开发人员设计出定时执行的功能模组。
.NET Framework提供的三种Timer类别,可以参考Bill叔的部落格:
但是当功能模组的开发,是以跨平台执行为目标来做设计的时候,因为不是每个平台都支援上列三种Timer,所以连带的在跨平台的专案中,也就不支援参考使用.NET Framework所提供的Timer类别。像是下图中所建立的Portable Class Library专案,就无法参考使用到System.Threading.Timer类别。
遇到这样跨平台的功能模组开发,该如何提供跨平台的定时执行功能呢?
解决方案
处理跨平台的定时执行功能,其实解决方案很简单,只要建立一个跨平台的Timer类别,用来提供定时执行的功能,就可以满足这个设计需求。
模组设计
建立Timer类别最简单的设计,就是开启一条独立的执行绪,透过这个执行绪定时去执行Callback函式,这就完成了Timer类别的功能设计。但是因为.NET Framework中所提供的System.Threading.Thread并不支援跨平台使用。所以执行绪的建立工作,必须改为可以跨平台使用的System.Threading.Tasks.Task来建立执行绪,这样才能符合跨平台的开发需求。
使用跨平台的System.Threading.Tasks.Task类别来建立的执行绪,并且使用这个执行绪来定时执行Callback函式,这就完成了跨平台Timer类别的功能设计。
模组下载
程式码下载:由此进入GitHub后,点选右下角的「Download ZIP」按钮下载。
(开启程式码的时候,建议使用Visual Studio所提供的「大纲->折叠至定义」功能来折叠程式码,以能得到较适合阅读的排版样式。)
物件程式
using CLK.Diagnostics;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CLK.Threading
{
public sealed class PortableTimer : IDisposable
{
// Fields
private readonly ManualResetEvent _executeThreadEvent = new ManualResetEvent(false);
private readonly Action _callback = null;
private readonly int _interval = 0;
// Constructors
public PortableTimer(Action callback, int interval)
{
#region Contracts
if (callback == null) throw new ArgumentNullException();
#endregion
// Require
if (interval <= 0) throw new ArgumentException();
// Arguments
_callback = callback;
_interval = interval;
// Begin
Task.Factory.StartNew(this.Execute);
}
public void Dispose()
{
// End
_executeThreadEvent.Set();
}
// Methods
private void Execute()
{
while (true)
{
// Wait
if (_executeThreadEvent.WaitOne(_interval) == true)
{
return;
}
// Execute
try
{
// Callback
_callback();
}
catch (Exception ex)
{
// Fail
DebugContext.Current.Fail(string.Format("Action:{0}, State:{1}, Message:{2}", "Callback", "Exception", ex.Message));
}
}
}
}
}
使用范例
CLK.Threading.Samples.No001 - 在Windows Store App中使用PortableTimer
-
使用范例
using System; using System.Threading; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace CLK.Threading.Samples.No001 { public sealed partial class MainPage : Page { // Fields private readonly object _syncRoot = new object(); private readonly SynchronizationContext _syncContext = null; private PortableTimer _operateTimer = null; // Constructors public MainPage() { // Base this.InitializeComponent(); // SyncContext _syncContext = SynchronizationContext.Current; } // Handlers private void MainPage_Loaded(object sender, RoutedEventArgs e) { lock (_syncRoot) { // Require if (_operateTimer != null) return; // Begin _operateTimer = new PortableTimer(this.Timer_Ticked, 500); } } private void MainPage_Unloaded(object sender, RoutedEventArgs e) { lock (_syncRoot) { // Require if (_operateTimer == null) return; // End _operateTimer.Dispose(); _operateTimer = null; } } private void Timer_Ticked() { System.Threading.SendOrPostCallback methodDelegate = delegate(object state) { // Display this.TextBlock001.Text = DateTime.Now.ToString(); }; _syncContext.Post(methodDelegate, null); } } }
-
执行结果
CLK.Threading.Samples.No002 - 在Windows Phone App中使用PortableTimer
-
使用范例
using System; using System.Windows; using Microsoft.Phone.Controls; using System.Threading; namespace CLK.Threading.Samples.No002 { public partial class MainPage : PhoneApplicationPage { // Fields private readonly object _syncRoot = new object(); private readonly SynchronizationContext _syncContext = null; private PortableTimer _operateTimer = null; // Constructors public MainPage() { // Base this.InitializeComponent(); // SyncContext _syncContext = SynchronizationContext.Current; } // Handlers private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e) { lock (_syncRoot) { // Require if (_operateTimer != null) return; // Begin _operateTimer = new PortableTimer(this.Timer_Ticked, 500); } } private void PhoneApplicationPage_Unloaded(object sender, RoutedEventArgs e) { lock (_syncRoot) { // Require if (_operateTimer == null) return; // End _operateTimer.Dispose(); _operateTimer = null; } } private void Timer_Ticked() { System.Threading.SendOrPostCallback methodDelegate = delegate(object state) { // Display this.TextBlock001.Text = DateTime.Now.ToString(); }; _syncContext.Post(methodDelegate, null); } } }
-
执行结果
CLK.Threading.Samples.No003 - 在WPF中使用PortableTimer
-
使用范例
using System; using System.Threading; using System.Windows; namespace CLK.Threading.Samples.No003 { public partial class MainWindow : Window { // Fields private readonly object _syncRoot = new object(); private readonly SynchronizationContext _syncContext = null; private PortableTimer _operateTimer = null; // Constructors public MainWindow() { // Base this.InitializeComponent(); // SyncContext _syncContext = SynchronizationContext.Current; } // Handlers private void Window_Loaded(object sender, RoutedEventArgs e) { lock (_syncRoot) { // Require if (_operateTimer != null) return; // Begin _operateTimer = new PortableTimer(this.Timer_Ticked, 500); } } private void Window_Unloaded(object sender, RoutedEventArgs e) { lock (_syncRoot) { // Require if (_operateTimer == null) return; // End _operateTimer.Dispose(); _operateTimer = null; } } private void Timer_Ticked() { System.Threading.SendOrPostCallback methodDelegate = delegate(object state) { // Display this.TextBlock001.Text = DateTime.Now.ToString(); }; _syncContext.Post(methodDelegate, null); } } }
-
执行结果