スレッド
最終更新:
atachi
スレッドの使用方法
スレッドはThreadクラスを使用して作成します。
Thread.Start()を呼び出したタイミングでスレッドが作成され実行を開始します。
スレッドとして処理を行うメソッドは静的メソッドやインスタンスメソッドを呼び出すことができます。
インスタンスメソッドの場合、thisにあたるインスタンスやそのインスタンスが持つメンバが複数のスレッドで共有されるため、それらへアクセスを行う場合は排他制御が必要となります。
public class MyThreadClass {
public static void StaticMethod() {
Console.WriteLine("静的メソッドの呼び出し");
Thread.Sleep(5000); // 5秒待機
Console.WriteLine("静的メソッド終了");
}
public void InstanceMethod() {
Console.WriteLine("インスタンスメソッドの呼び出し");
Thread.Sleep(5000); // 5秒待機
Console.WriteLine("インスタンスメソッド終了");
}
}
public class MainApp {
public static int Main(String[] args) {
// インスタンスを作成し、インスタンスメソッドを別スレッドで実行
MyThreadClass obj = new MyThreadClass();
Thread instanceThread = new Thread( obj.InstanceMethod );
instanceThread.Start();
// 静的メソッドを別スレッドで実行
Thread staticThread = new Thread( MyThreadClass.StaticMethod );
staticThread.Start();
return 0;
}
}
Thread.Start()では引数を与えることができます。
次のようにスレッドとして実行するメソッドが2つの引数を持つ場合には、2つの値を設定できます。
public class MyThreadClass {
public static void StaticMethod(string text,int value) {
Console.WriteLine("引数を使用してスレッドを実行中");
Console.WriteLine("text引数={0}" , text);
Console.WriteLine("value引数={0}" , value);
}
public void runThread() {
Thread th = new Thread(StaticMethod);
th.Start("スレッドから実行中",10);
}
}
また、スレッドからスレッド呼び出し元へ値を返したい場合、コールバックを使用すると便利です。
コールバックとしてデリゲートを定義し、Thread.Start()を呼び出し時にデリゲートを渡すという単純な仕組みで実装しています。
public class MyThreadClass {
delegate void procCallback(int value);
public static void StaticMethod(string text,int value,procCallback callback) {
Console.WriteLine("引数を使用してスレッドを実行中");
Console.WriteLine("text引数={0}" , text);
Console.WriteLine("value引数={0}" , value);
if( callback != null ) {
callback(1);
}
}
public void runThread() {
Thread th = new Thread(StaticMethod);
th.Start("スレッドから実行中",10,new procCallback(ThreadCallbackMethod));
}
public static void ThreadCallbackMethod(int i) {
Console.WriteLine("スレッドからのコールバック={0}" , i);
}
}
スレッドの待機
Thread.Sleep()を使用すると現在のスレッドを任意の時間だけ処理を待機させることができます。
スレッドの中断要求
任意のスレッドのインスタンスからThread.Interrupt()を呼び出すと、対象のスレッドを中断させることができます。
Interruptが実行されたスレッドでは直ちにThreadInterruptedExceptionがスローされます。ただし、スレッドがどんな状態でもこのスローが呼び出されるわけではありません。
なぜなら、処理の途中でこの例外がスローしてしまうと、その処理の箇所から復帰することはできないためです。
そのため、スレッドがブロッキングされている箇所まで実行された後、ThreadInterruptedExceptionがスローされる事になっていることに注意が必要です。
class InterruptThreadTest {
public void InstanceMethod() {
try{
Console.WriteLine("InstanceMethodからの呼び出し");
Thread.Sleep(Timeout.Infinite); // スレッドのブロッキングが発生し、ここでInterruptが呼び出される。
}catch(ThreadInterruptedException e) {
Console.WriteLine("スレッドが中断されました");
}
}
}
public void main() {
InterruptThreadTest obj = new InterruptThreadTest();
Thread th = new Thread(obj.InstanceMethod);
th.Start();
Thread.Sleep(2000);
th.Interrupt();
th.Join();
}
上記のコードを実行すると
InstanceMethodからの呼び出し (2秒間) スレッドが中断されました
と表示されてアプリケーションは終了します。
スレッドの中止要求
任意のスレッドを中止させたい場合は、インスタンスからThread.Abort()を呼び出します。
スレッド側はInterruptと同じく例外をスローするのでそれをキャッチし、適切な終了処理を実行した後、スレッドを終了させます。
Abortの場合はThreadAbortException がスローされます。
スローされるタイミングはInterruptと同じくスレッドがブロックされている時に行われます。
Abort()を呼び出す側は、Abort()を呼び出してもその瞬間にスレッドが終了するわけではありません。
Abortはあくまでも中止要求であり、スレッドによってはスレッドが終了する際にリソースの解放など様々な後処理を行う必要があり、スレッドが完全に終了するまで時間がかかることがあります。
スレッドの呼び出し元でスレッドが停止するまで待機したい場合は、Abort()を呼び出した後にJoin()を呼び出しスレッドの終了を待機します。
UIスレッドとの関連
コントロールにコレクションをバインドし、そのコレクションをUIスレッドから使用とするとアプリケーションが強制終了してしまう。
この問題を回避するには、上記サイトにもあるようにコレクションの操作はDispatcherで行うのがよいそうです。
class DispatchObservableCollection<T> : ObservableCollection<T> {
public DispatchObservableCollection() {
InitializeEventDispatcher();
}
public DispatchObservableCollection(IEnumerable<T> collection)
: base(collection) {
InitializeEventDispatcher();
}
public DispatchObservableCollection(List<T> list)
: base(list) {
InitializeEventDispatcher();
}
private void InitializeEventDispatcher() {
// インスタンスが作られた時のDispatcherを取得
this.EventDispatcher = Dispatcher.CurrentDispatcher;
}
// CollectionChangedイベントを発行するときに使用するディスパッチャ
public Dispatcher EventDispatcher {
get;
set;
}
private bool IsValidAccess() {
return EventDispatcher == null || EventDispatcher.Thread == Thread.CurrentThread;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
if (IsValidAccess()) {
base.OnCollectionChanged(e);
} else {
// Dispatcher
Action<NotifyCollectionChangedEventArgs> changed = OnCollectionChanged;
this.EventDispatcher.Invoke(changed, e);
}
}
}
- http://d.hatena.ne.jp/okazuki/20100112/1263267397
- http://msdn.microsoft.com/ja-jp/magazine/cc163328.aspx
タイマー使用方法
Timerクラスのインスタンスは保持しておく必要があります。