画像ファイル
画像ファイル BackgroundWorkerの魅力(1)
VB.NET C#全般
1羊の皮を着た狼 VB.NET
2Form1、Form2の相互参照
3Form1、Form2の相互参照 2
4VB.NET C# データ型の基本
5VB.NET C# 文字列
6VB.NET タイマー精度
7BackgroundWorkerの魅力1..
8BackgroundWorkerの魅力2..
9VB6のタイマー
10コントロールの配列をインデクサ..
11コントロールの配列はジェネリク..
12インデクサ(C#、VB.NET)
13インデクサでBit操作
14Unicode 入門
15デリゲート入門
16マルチスレッド入門
17イベント入門
18デリゲートとイベント
18インターフェースの基本

RichTextBox関係
1RichTextBoxの不思議
2テキスト色付け高速化計画
3VB.NET RichTextBox1
4VB.NET RichTextBox 2

RS-232C関係
1RS-232Cの基礎
2RS-232Cの何が変わった..
3SerialPortクラス
4Unicode(ユニコード)の壁
5マルチスレッドの壁
6RS-232C サンプルコード
7RS-232CのHEXモニタ
8RS-232C 送信モジュール
9RS-232Cのループテスト
10RS-232Cのピンチェンジ..

Socket通信
1C#、VB2005 でSocket通信
2サーバー 複数接続

プロセス間通信
1プロセス間通信(送信側)
2プロセス間通信(受信側)


質問、意見はこちらに
画像ファイル

正確なタイマーがほしい
プログラムを組んでいると、時々正確なタイマーが欲しくなる。
VB6はほぼシングルタスクなので、APIの正確な時間を取得しても、 正確なタイマーを作ることは難しい。
VB5の頃はマルチメディア・タイマーなるものが有って、タイマーが別スレッドで走っていた、
しかしVB6になってネイテブコードのコンパイルでは、殆どマルチスレッドは無理になってしまった。
ところが、VB.NET、C#になってこのマルチスレッドが使用できるようになった。
ただし使い方に色々制約があり結構面倒である。
所がである、VB2005、C#にはBackgroundWorkerなるものが出来、マルチスレッドが簡単に使えるのだと言う、
これはかなり耳よりな話だ、さっそくこのBackgroundWorkerで今まで欲しかったタイマーを作ってみよう。

概観と仕様
下のイメージがその概観である、チョッと欲張って時計とストップウォッチを入れておこう。
ストップウォッチはLapの機能を付ける。
Lap中はタイマーの表示の更新はしないが、其の横に黄色のドットが1秒毎に点滅する。
画像ファイル
コードのサンプルと説明

画面デザイン
ラベルの一つには時間を表示させ、もう一つににはストップウォッチの経過時間を表示させる。
ボタンは、スタート、ラップ、ストップの計3つである。
さて画像を良く見てほしいのだが、時間と経過時間の表示の中に、H、M、S、mSの表示がある、
実はこれらも小さいラベルを貼り付けてある。 時間表示用のラベルと、Lap時に点滅するPictureBoxを置いてある。
それとツールボックスからBackgroundWorker1(backgroundWorker1)を貼り付け
其のプロパティWindowのイベント表示メニューからDoWorkをクリックして、コードに
BackgroundWorker1_DoWork(backgroundWorker1_DoWork)イベントを追加しておこう。
もちろんボタンのイベントも追加しておく。

デリゲートの宣言と共通変数
先ずストップウオッチの状態を表す定数を宣言する。
更にこの定数を保持する、変数「States」を宣言する。
この変数はボタンが押されて、Start、Stop、Lapのいずれかの状態を保持する。
又秒の変化を知るために前の秒を保持する変数「Second」を宣言している。
後は、Labelに書き込むためのデリゲートとドット表示用のPictureBoxを 表示非表示にするデリゲートを宣言する。
BackgroundWorkerの実行は別スレッドで行なわれる為にメインスレッドのLabelの表示を 変えたりPictureBoxの表示を切り替える為にはデリゲートが必要である。
C#のコード
//状態を表す定数
const int STOP = 0;
const int START = 1;
const int LAP = 2;

//上の3つ状態を保持します
int States = 0 ;

//秒を保持します
int Second = 0;

//現在の時刻の表示と、タイマーの表示に使用されるデリゲート
delegate void dlgSetString(object lbl,string text);

//ラップ時のドットの表示非表示に使用されます
delegate void dlgGene();

//ストップウオッチクラス
System.Diagnostics.Stopwatch MyStopWatch = new System.Diagnostics.Stopwatch();
VB2005のコード
'状態を表す定数
Const sSTOP As Integer = 0
Const sSTART As Integer = 1
Const sLAP As Integer = 2

'上の3つ状態を保持します
Dim States As Integer = 0

'秒を保持します
Dim Second As Integer = 0

'現在の時刻の表示と、タイマーの表示に使用されるデリゲート
Delegate Sub dlgSetString(ByVal lbl As Object, ByVal text As String)

'ラップ時のドットの表示非表示に使用されます
Delegate Sub dlgGene()

'ストップウオッチクラス

Dim MyStopWatch As System.Diagnostics.Stopwatch = New System.Diagnostics.Stopwatch()

フォームロード時の処理
フォーム起動時にBacgroundWorkerが立ち上がる様になっている。
RunWorkerAsync()
驚くことに、この一行で第2スレッドが立ち上がり、この中にコードが書けるのである。
このセカンドスレッドの中のコードは、BackgroundWorker1_DoWork
の中にに書かれている。
C#のコード  フォームのロード時のコード
//コンストラクタ
public FomStopwatch()
{
InitializeComponent();

//BackgroundWorkerを起動します
backgroundWorker1.RunWorkerAsync();
}
VB2005のコード  フォームのロード時のコード
Private Sub ForStopwatch_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'BackgroundWorkerを起動します()
BackgroundWorker1.RunWorkerAsync()
End Sub


フォームアンロード時の処理
フォームアンロード時は第2スレッドも閉じる必要が有る
WorkerSupportsCancellation = True
CancelAsync()
この2行で第2スレッドを閉じている。
C#  フォームアンロード時のコード
//フォームが閉じられるとき発生します
private void FomStopwatch_FormClosing(object sender, FormClosingEventArgs e)
{
//キャンセルを有効にする
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.CancelAsync();
backgroundWorker1.Dispose(); }
VB2005のコード  フォームアンロード時のコード
Private Sub FomStopwatch_FormClosing(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.FormClosingEventArgs) _
Handles MyBase.FormClosing
'キャンセルを有効にする
BackgroundWorker1.WorkerSupportsCancellation = True
BackgroundWorker1.CancelAsync()
BackgroundWorker1.Dispose()
End Sub

ボタン押下の処理
ボタン押下時はそれぞれ前の状態(States)を見て処理を変えている。
Statesは別スレッドの中からも参照され、其の状態を判断して、何を表示するかを決めている。
注意する点は、LapからStart又はStopに移行した時に、ドットの点滅がタイミングにより残るので これを非表示にする点と、LapからStopに変わった時に最終の時間を表示する点である。
コードは全体のコードを参照願いたい。

BackgroundWorkerの別スレッドから呼ばれるメソド。
BackgroundWorkerの別スレッドBackgroundWorker1_DoWorkからは2つのメソドが呼ばれる。
1つ目はラベルに経過時間を表示するメソドであり、これは時間表示のラベルの表示用も兼ねている。
C#のコード
private void ShowTime(object sender,string strTime)
VB2005のコード
Private Sub ShowTime(ByVal sender As Object, ByVal strTime As String)
第一引数はobject(Object)にしてあるが、これはLabelでも良くLabelの場合はキャストの必要は無い。
2つ目のメソドはLap表示の為のドットの表示非表示を切り替えるメソドである。
C#のコード BackgroundWorkerの別スレッドから呼ばれるメソド。
//デリゲートで別スレッドから呼ばれてラベルに現在の時間又は
//ストップウオッチの時間を表示する
private void ShowTime(object sender,string strTime)
{
Label lbl = (Label)sender; //objectをLabelにキャストする
lbl.Text = strTime;
}

//Lapの場合一秒ごとに別スレッドからデリゲートに呼ばれ
//ドットの表示非表示を行なう
private void ChangePicColor()
{
picDot.Visible = !picDot.Visible; }
VB2005のコード BackgroundWorkerの別スレッドから呼ばれるメソド。
'デリゲートで別スレッドから呼ばれてラベルに現在の時間又は
'ストップウオッチの時間を表示する
Private Sub ShowTime(ByVal sender As Object, ByVal strTime As String)
Dim lbl As Label = DirectCast(sender, Label) 'objectをLabelにキャストする
lbl.Text = strTime
End Sub

'Lapの場合一秒ごとに別スレッドからデリゲートに呼ばれ
'ドットの表示非表示を行なう
Private Sub ChangePicColor()
PicDot.Visible = Not PicDot.Visible
End Sub

セカンドスレッドの処理
BackgroundWorkerの別スレッドの中はループになっていて、秒が更新されるとびょの表示を、 ストップウオッチが作動してると経過時間の表示を、又Lap時はドットの点滅を行なう。
ただしこれらの表示はいずれもメインスレッドで行なう為に、デリゲートを使って、メソドを 呼び出している。
C#のコード
this.Invoke(new dlgGene(ChangePicColor), new object[] { });
VB2005のコード
Me.Invoke(New SetLabelTextDelegate(AddressOf SetLabelTime), strTime)
上の方法は匿名デリゲートを呼ばれる方法であり
C#のコード
dlgSetString SetString = new dlgSetString(ShowTime);
this.Invoke(SetString,lblTimer, eTime);
VB2005のコード
Dim SetString As dlgSetString = New dlgSetString(AddressOf ShowTime)
Me.Invoke(SetString, LblTimer, eTime)
という書き方と比較してデリゲートのインスタンスの名前を付けずにデリゲートを呼び出す為に匿名と呼ばれているのだろう。

更に、CPUの使用率を下げる為に下のコードを追加している。
C#のコード
System.Threading.Thread.Sleep(1);
VB2005のコード
System.Threading.Thread.Sleep(1)

セカンドスレッドでの処理のコード
C#のコード
//別スレッド上で行なわれる処理
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//スレッドが停止状態でなければ
while (!backgroundWorker1.CancellationPending)
{
//1msecのウエイト、CPUの使用率を下げる
System.Threading.Thread.Sleep(1);

try
{
//秒が変更されたら
if (Second != DateTime.Now.Second)
{
//時間を文字列に直す
string strTime = stringFormat("{0:00}", DateTime.Now.Hour)
+ ":" + stringFormat("{0:00}", DateTime.Now.Minute)
+ ":" + stringFormat("{0:00}", DateTime.Now.Second) + ":"
+ stringFormat("{0:000}", DateTime.Now.Millisecond);
//匿名デリゲートで現在の時間をラベルに表示する
this.Invoke(new dlgSetString(ShowTime), new object[]
{ lblTime, strTime });
if (States == LAP)
{
//匿名デリゲートでドットの表示非表示情報を渡す
this.Invoke(new dlgGene(ChangePicColor),
new object[] { });
}
//秒の保持
Second = DateTime.Now.Second;
}

//ストップウオッチが動作していたら時間を表示する
if (States == START)
{
//ストッップウオッチの時間を文字に直す
string eTime = string.Format("{00:00:00:00:000}",
MyStopWatch.ElapsedMilliseconds);
//匿名デリゲートで時間を渡す
this.Invoke(new dlgSetString(ShowTime),
new object[] { lblTimer, eTime });
//dlgSetString SetString = new dlgSetString(ShowTime);
//this.Invoke(SetString,lblTimer, eTime);
}
}
catch
{
//このエラーはフォームを閉じたときに発生します
}
}
}
VB2005のコード
    '別スレッド上で行なわれる処理
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles BackgroundWorker1.DoWork

'スレッドが停止状態でなければ
While (Not BackgroundWorker1.CancellationPending)
'1msecのウエイト、CPUの使用率を下げる
System.Threading.Thread.Sleep(1)

Try
If Second <> DateTime.Now.Second Then
'時間を文字列に直す
Dim strTime As String = String.Format("{0:00}", DateTime.Now.Hour) _
+ ":" + String.Format("{0:00}", DateTime.Now.Minute) _
+ ":" + String.Format("{0:00}", DateTime.Now.Second) + ":" _
+ String.Format("{0:000}", DateTime.Now.Millisecond)
'匿名デリゲートで現在の時間をラベルに表示する
Me.Invoke(New dlgSetString(AddressOf ShowTime), _
New Object() {LblTime, strTime})
If States = sLAP Then
'匿名デリゲートでドットの表示非表示情報を渡す
Me.Invoke(New dlgGene(AddressOf ChangePicColor), _
New Object() {})
End If
'秒の保持
Second = DateTime.Now.Second
End If
'ストップウオッチが動作していたら時間を表示する
If States = sSTART Then

'ストッップウオッチの時間を文字に直す
Dim eTime As String = String.Format("{00:00:00:00:000}", _
MyStopWatch.ElapsedMilliseconds)
'匿名デリゲートで時間を渡す
Me.Invoke(New dlgSetString(AddressOf ShowTime), _
New Object() {LblTimer, eTime})

'Dim SetString As dlgSetString = New dlgSetString(AddressOf ShowTime)
'Me.Invoke(SetString, LblTimer, eTime)
End If
Catch

'このエラーはフォームを閉じたときに発生します
End Try
End While
End Sub


同じコードをマルチスレッドで書いた。
同じ仕様で作ったVB6のコード
画像ファイル