ユーザ定義メソッドを連続発行するタスクを実行しタイマー的動作を実現する
タイマー関係の投稿が連続しております。無限ループの中でディレイ Task.Delay をいれながらメソッドを実行するとタイマー的動作を実施できます。
System.Windows.Forms.Timer は以前の投稿でも指摘したようにCPUが忙しくなると処理が後回しにされがちです。それに比較するとこの方法は比較的軽く UI スレッドの影響をうけにくいです。
System.Threading.Timer (優先度が高い)
→ 本方式(そこそこ優先度が中ぐらい)
→ System.Windows.Forms.Timer (優先度が低い)
なんだか定性的な説明ですが察してください(笑)。

Form1 に下記のコンポーネントを配置してください。
・timer1
・Button button10 (タイマの開始)
・Button button11 (タイマの停止)
・pictureBox1
それぞれに Ticks イベントとClick イベントと Paint イベントを設定してください。
・Button button20 (疑似タイマの開始)
・Button button21 (疑似タイマの停止)
・pictureBox2
それぞれに Click イベントと Paint イベントを設定してください。
キャンセルトークンなんか使わなくても bool 型の変数で FlagRequestCancel 的な変数を true/false すればいいのではと思う方々もいると思います。bool で判断する場合は、いろいろなスレッドからこちらをアクセスするときに排他処理がうまくいかなくてデッドロックになる可能性があるので使用してはいけません。
int FlagReqestCancel; のように bool ではなく int で定義して Interlocked.Exchange( ref( FlagRequstCancel ), 0 または 1 ); として、Interlocked.CompareExchange( ref( FlagRequstCancel ) ) をつかって int 型のフラグ変数を評価してやればマルチスレッド対応になると思います。未テストです、興味のあるかたは十分テストしてからどうぞ。
using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace aaa
{
public partial class Form1 : Form
{
// タイマ間隔.
const int MSEC_INTERVAL = 100;
// 濃度の増加レベル.
const int TICKS_LEVEL = 16;
// それぞれのタイマのカウンタ.
int GlobalCounter1 = 0;
int GlobalCounter2 = 0;
private CancellationTokenSource Cts;
public Form1()
{
InitializeComponent();
}
private void Form1_Load( object sender, EventArgs e )
{
// System.Windows.Forms.Timer を設定する.
timer1.Interval = MSEC_INTERVAL;
}
private void Form1_FormClosing( object sender, FormClosingEventArgs e )
{
}
private void button10_Click( object sender, EventArgs e )
{
timer1.Start();
}
private void button11_Click( object sender, EventArgs e )
{
timer1.Stop();
}
private void timer1_Tick( object sender, EventArgs e )
{
GlobalCounter1 += TICKS_LEVEL;
if ( GlobalCounter1 > 255 )
{
GlobalCounter1 = 0;
}
pictureBox1.Invalidate( true );
}
private void pictureBox1_Paint( object sender, PaintEventArgs e )
{
PictureBox pbx = ( sender as PictureBox );
if ( pbx == null )
{
return;
}
// 青レベルにだけ反映する.
byte level = (byte)( GlobalCounter1 );
Color c = Color.FromArgb( 0x00, 0x00, level );
// 塗りつぶす.
using ( SolidBrush sb = new SolidBrush( c ))
{
e.Graphics.FillRectangle( sb, pbx.ClientRectangle );
}
}
// ユーザが記述するタイマ的動作.
private void UserDefineTimerAction()
{
// レベルを徐々に増やす.
GlobalCounter2 += TICKS_LEVEL;
if ( GlobalCounter2 > 255 )
{
GlobalCounter2 = 0;
}
BeginInvoke(( MethodInvoker )(() =>
{
// ピクチャボックスの再描画要請をする.
pictureBox2.Invalidate( true );
}));
}
// Task.Delayを連続発行してタイマ的動作をさせる.
private async Task TheTimerLoop( int interval, Action action, CancellationToken token )
{
// 無限ループ.
for (;;)
{
// キャンセルトークンが発行されたら無限ループを脱出する.
if ( token.IsCancellationRequested )
{
break;
}
else
{
if ( action != null )
{
action.Invoke();
}
}
// 無限ループ内で遅延させて、タイマ間隔とする.
await Task.Delay( interval, token );
}
}
private void button20_Click( object sender, EventArgs e )
{
// キャンセルトークンが生成されていたらタイマ的動作が実行中である.
if ( Cts != null )
{
// キャンセル要請を発行する.
Cts.Cancel();
}
// キャンセルトークンを生成する.
Cts = new CancellationTokenSource();
// UserDefineTimerAction はタイマ的な動作をさせたいメソッドをユーザが書く.
Task.Run( () => TheTimerLoop( MSEC_INTERVAL, UserDefineTimerAction, Cts.Token ), Cts.Token );
}
private void button21_Click( object sender, EventArgs e )
{
// キャンセルトークンが生成されていたらタイマ的動作が実行中である.
if ( Cts != null )
{
// キャンセル要請を発行する.
Cts.Cancel();
}
}
private void pictureBox2_Paint( object sender, PaintEventArgs e )
{
PictureBox pbx = ( sender as PictureBox );
if ( pbx == null )
{
return;
}
// 緑レベルにだけ反映する.
byte level = (byte)( GlobalCounter2 );
Color c = Color.FromArgb( 0x00, level, 0x00 );
// 塗りつぶす.
using ( SolidBrush sb = new SolidBrush( c ))
{
e.Graphics.FillRectangle( sb, pbx.ClientRectangle );
}
}
}
}