2014年4月24日木曜日

[WPF][Silverlight][C#]UIスレッドアクセス方法

応答なし

重たい処理を行う場合、全てUIスレッドで行うと画面が固まってしまい、「応答なし」でまっしろになります。応答性の高い画面を作成するのであれば、この重たい処理は別スレッドで行うようにしなければなりません。

別スレッドの起こし方

Threadクラスを使うやり方と、Taskクラスを使うやり方がありますね。

            var thread = new Thread(delegate()
            {
                // 別スレッドの処理を記述
            });
            thread.Start();

これのメリットはキャンセル出来ることでしょうか。

            Task.Factory.StartNew(() =>
            {
                // 別スレッドの処理を記述
            });


こちらのメリットは複数タスクを管理しやすいとかですかねー。


UIスレッドへの合流

別スレッドにて行った処理の結果を画面上に反映させる場合、通常アクセス出来ず例外が発生するので、UIスレッドに合流してから反映処理を行う必要があります。(MVVMパターンならある程度いけますが)

通常であれば、Dispatcher.BeginInvoke ですね。

    this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
        {
            // UIスレッド
        }));



MVVM パターンであれば、Application.Current から Dispatcher を辿る感じでしょうか。

実は Dispatcherを経由しなくても、UIスレッド上で作成しておいた TaskFactory を使えば、同等の事が出来るようになります。

        /// <summary>
        /// UIスレッドタスク
        /// </summary>
        private TaskFactory uiTask;

            // 事前に登録しておく(下記処理はUIスレッドで実施する必要がある)
            this.uiTask = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());

            this.uiTask.StartNew(() =>
            {
                // UIスレッドにて動作する

            });


Appクラスに上記をスタティックプロパティとして用意しておけば、便利かもしれません。



2 件のコメント:

  1. 自分も勉強中なのですが、大事なことなので。
    Threadのキャンセルも直接生成もあまりよろしくないです。(OS・CPUに負担がかかるだけなのがほとんど)
    実際にスレッド自体を利用するなら、ThreadPoolクラスを用いることがほとんどです。
    TaskScheduler.Default()はThreadPoolを指してますので、Taskも普段はThreadPoolを使ってるわけですね。

    Taskの中断はSystem.Threading.CancelleationTokenクラス、
    その生成・操作は(名前空間は同じ)CancellationTokenSourceクラスを利用します。
    少々実装が面倒なのは確かですが、TPLは中断するのにこの2つのクラスを使うことを前提に設計されています。

    最後に。
    async・awaitを勉強したらいいことが起きるかもしれないですよ。

    返信削除
    返信
    1. 悠輔様
      スレッドについての大事なご指摘、ありがとうございます!
      ご指摘あって改めて読み直したらダメダメですね。
      もともとの意図としては、記事のタイトル通り、イベントドリブンではなくて通信などバックグラウンドで処理させているものからの通知をUIスレッドに通知する方法として Dipatcher が一般的ですが、UIスレッドで生成したTaskFactoryでも合流出来るということを言いたかっただけでしたー(言い訳)。
      とはいえ、Task がキャンセル出来ることも碌に調べずに書いたり、ThreadPool を使わない安易なやり方で書いてしまったのは反省です。
      近いうちに書き直しますm(_ _)m

      削除