sky’s 雑記

主にAndroidとサーバーサイドの技術について記事を書きます

EXISTS と INNER JOIN のパフォーマンスについて

EXISTSをINNER JOINに置き換えるとパフォーマンスが上がるという主張をよく見かけますが、 ちょっと怪しい記事が多かったので調べました。

環境

他のDBでは確認していないのでMySQLに限定した話題です。

usersテーブルは複数のroom_usersを持ちます、レコード数は100万程度で試しています。

sh-4.4# mysql --version
mysql  Ver 8.0.30 for Linux on aarch64 (MySQL Community Server - GPL)
erDiagram

users {
  id integer PK
}

room_users {
  id integer PK
  user_id integer FK
}

users ||--o{ room_users : has_many

事前調査

調べていくと冒頭の主張とは異なるものの確度が高そうな情報を見つけた。

DBサーバーの種類に言及はなかったがStack Overflow[1][2]とQuora[3]の質問の回答をまとめると

  • uniqueなカラムについてINNER JOINする場合はEXISTSとパフォーマンスは変わらない
  • DISTINCTが適用されるレコードに対してINNER JOINする場合はEXISTSのほうがパフォーマンスが良い

INNER JOINとEXISTSはパフォーマンスは変わらず、重複削除の必要があるDISTINCT + INNER JOINになるとパフォーマンスが悪くなるとのこと。

実行計画

EXISTS, INNER JOIN, DISTINCT + INNER JOINそれぞれについて実行計画を出力して比較する。

EXISTS

select `users`.* from `users` where `users`.`id` != 1 and exists (select * from `room_users` where `users`.`id` = `room_users`.`user_id` and `room_users`.`room_id` in (10,11,12,13,14)) order by `users`.`updated_at` desc limit 30;

INNER JOIN

select `users`.* from `users` inner join `room_users` on `room_users`.`user_id` = `users`.`id` and `room_users`.`room_id` in (10,11,12,13,14) where `users`.`id` != 1 order by `users`.`updated_at` desc limit 30;

DISTINCT + INNER JOIN

select distinct `users`.* from `users` inner join `room_users` on `room_users`.`user_id` = `users`.`id` and `room_users`.`room_id` in (10,11,12,13,14) where `users`.`id` != 1 order by `users`.`updated_at` desc limit 30;

Optimizer Trace

 SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G

出力された join_preparation で最終的に展開されるクエリは以下のようになる、semi joinは重複を取り除いた結果を結合するというもので、結合後に重複を取り除くよりも効率が良くDISTINCT + INNER JOINよりも効率が良いとのこと[4]

長くなるので省略するが considered_execution_plans に表示されるEXISTSとINNER JOINの実行計画は等しかったため、EXISTSとINNER JOINのパフォーマンスの差は重複を取り除くタイミングによることになる。

EXISTS

 select `users`.`id` AS `id`... from `users` semi join (`room_users`) where ((`users`.`id` <> 1) and (`room_users`.`room_id` in (10,11,12,13,14)) and (`users`.`id` = `room_users`.`user_id`)) order by `users`.`updated_at` desc limit 30

INNER JOIN

select `users`.`id` AS `id`... from `users` join `room_users` where ((`users`.`id` <> 1) and (`room_users`.`user_id` = `users`.`id`) and (`room_users`.`room_id` in (10,11,12,13,14))) order by `users`.`updated_at` desc limit 30

DISTINCT + INNER JOIN

select distinct `users`.`id` AS `id`... from `users` join `room_users` where ((`users`.`id` <> 1) and (`room_users`.`user_id` = `users`.`id`) and (`room_users`.`room_id` in (10,11,12,13,14))) order by `users`.`updated_at` desc limit 30

オプティマイザトレースの読み方は[5]-[7]に詳しい。

まとめ

MySQL8.0.30では

  • レコードの重複の削除が必要ない場合EXISTSとINNER JOINのパフォーマンスは変わらない
  • レコードの重複の削除が必要な場合EXISTSに比べINNER JOINのパフォーマンスは悪い

参考

[1] sql - Can an INNER JOIN offer better performance than EXISTS - Stack Overflow

[2] IN vs. JOIN vs. EXISTS at EXPLAIN EXTENDED

[3] Does select distinct slow down a query? - Quora

[4] MySQL :: MySQL 5.7 Reference Manual :: 8.2.2.1 Optimizing Subqueries, Derived Tables, and View References with Semijoin Transformations

[5] The MySQL Query Optimizer Explained Through Optimizer Trace | Percona Live - Open Source Database Conference 2019

[6] 非公式MySQL 8.0オプティマイザガイド by yakst

[7] 漢(オトコ)のコンピュータ道: オプティマイザトレースによるちょっとディープな快適チューニング生活

MySQLコンテナのready for connectionsを保証する

マイグレーションなどアプリケーションからMySQLを操作する場合にMySQLの起動を保証する方法についてです。

概要

docker exec db mysql -u${DB_USER} -p${DB_PASSWORD}MySQLのready for connectionsを保証できます。

      - name: wait for connections ready
        env:
          DB_USER: ${{ secrets.DB_USER }}
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
        run: |
          while ! docker exec db mysql -u${DB_USER} -p${DB_PASSWORD}; do
            sleep 1s
          done
        shell: bash

背景

MySQLとアプリケーションを別々のコンテナで管理しておりMySQL起動後にアプリケーションからDBを操作するケースで、 以下のようにMySQL起動後にアプリケーションからDBを操作しようとすると SQLSTATE[HY000] [2002] Connection refused となり失敗する。

      - name: launch mysql
        run: docker run -d -p 3306:3306 --name=db mysql/mysql-server:8.0

      - name: setup db
        run: docker run db php artisan migrate

これはMysqlを起動して即時ready for connectionsにならないことが原因なので、上記の処理を行うためにはsetup dbの前段でMySQLがready for connectionsになっていることを保証する必要がある。

docker-composeを利用するのが一番手っ取り早そうだったが、Github-hostedなubuntuにdocker-composeコマンドが存在せずdockerコマンドのみインストールされていたのでdockerコマンドで実現することを考える。

方法

うまくいく方法

1. mysql -u${DB_USER} -p${DB_PASSWORD}

冒頭で記載した方法。 接続に成功する場合docker execの終了コードが0になるのでこの方法で判定できる。

うまくいかない方法

1. docker inspect -f {{.State.Running}} db

コンテナのステータスがRunningであればsetup dbを実行する方法。 コンテナの起動とMySQLのready for connectionsは関係がないのでうまくいかない。 コンテナのステータスがRunningになるほうが早くConnection refusedになる。

2. mysqladmin ping

mysqladminのpingコマンドを利用するネットで検索するとよく見つかる方法、healthcheckでこのコマンドを実行しているdocker-composeもあるが今回のユースケースではうまくいかない。 公式[1]に記載があるとおりAccess Deniedでも0が返却されるので実行結果のstatusを見るだけでは100%ready for connectionsを保証することはできない。

Check whether the server is available. The return status from mysqladmin is 0 if the server is running, 1 if it is not. This is 0 even in case of an error such as Access denied, because this means that the server is running but refused the connection, which is different from the server not running.

未確認な方法

1. 実行ログを確認する

Qiitaで投稿されていた方法[2]で未確認だが雰囲気うまくいきそう。

参照

[1] MySQL :: MySQL 8.0 Reference Manual :: 4.5.2 mysqladmin — A MySQL Server Administration Program

[2] docker-compose を CI で実行するとき, MySQL の起動完了まで確実に待つ - Qiita

Github Actionsでワンショットタスクを実行する

最近はサーバーサイドの仕事が増えているので、 記事もサーバーサイドよりの技術が増えるかもしれません。

ECS Fargateにアプリケーションをデプロイする際、 あわせてDBスキーマの更新マイグレーションを行いたいユースケースについてです。

結論

steps:
      - uses: actions/checkout@v2

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN }}
          aws-region: ap-northeast-1
          role-session-name: GithubActionDeployment

      - name: Install AWS CLI
        shell: bash
        run: |
          if ! [ -x "$(command -v aws)" ]; then
            curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
            unzip awscliv2.zip
            sudo ./aws/install --update
            aws --version
          fi

      - name: ECS run migration task
        env:
          task_definition: # task definition arn
          cluster: # cluster
          vpc_configuration: # vpn conf
          container_overrides: '{"containerOverrides": [{"name": "web", "command": ["php", "artisan", "migrate"] }]}'
        run: |
          task_arn=$(aws ecs run-task \
            --region ap-northeast-1 \
            --launch-type FARGATE \
            --cluster '${{ env.cluster }}' \
            --network-configuration 'awsvpcConfiguration=${{ env.vpc_configuration }}' \
            --task-definition '${{ env.task_definition}}' \
            --overrides '${{ env.container_overrides }}' \
            --query tasks[0].taskArn --output text)

背景

ワンショットタスク

DBマイグレーションのようなコマンド実行後に正常終了するECSタスクのことを指しています。ECSタスクを構成するessentialなコンテナが全て終了するとECSタスクも終了するため、起動後正常終了するDockerコンテナと考えて良いです。 通常のECSタスクを構成するコンテナはCMD ["nginx", "-g", "daemon off;"] のようにフォアグラウンドで起動し続けるため意識することがありませんでしたが、バックグラウンドで起動するプロセスや起動後終了するプロセスの場合CMD実行後コンテナは正常終了します。 よってGithub ActionsでDBマイグレーションのようなワンショットタスクを実行したい場合コンテナのCMDにマイグレーションコマンドを指定したECSタスクを作成すれば良いことになります。

方法

run-task

aws cliのrun-taskコマンド[1]のoverridesオプションでCMDなどコンテナの定義を上書きすることができるのでこれを採用します。

{
  "containerOverrides": [
    {
      "name": "string",
      "command": ["string", ...],
      "environment": [
        {
          "name": "string",
          "value": "string"
        }
        ...
      ],
      "environmentFiles": [
        {
          "value": "string",
          "type": "s3"
        }
        ...
      ],
      "cpu": integer,
      "memory": integer,
      "memoryReservation": integer,
      "resourceRequirements": [
        {
          "value": "string",
          "type": "GPU"|"InferenceAccelerator"
        }
        ...
      ]
    }
    ...
  ],
  "cpu": "string",
  "inferenceAcceleratorOverrides": [
    {
      "deviceName": "string",
      "deviceType": "string"
    }
    ...
  ],
  "executionRoleArn": "string",
  "memory": "string",
  "taskRoleArn": "string",
  "ephemeralStorage": {
    "sizeInGiB": integer
  }
}

例としてLaravelのmigrationをワンショットで実行することを考えるとコマンドは以下のようになります。

aws ecs run-task \
            --region ap-northeast-1 \
            --launch-type FARGATE \
            --cluster '${{ env.cluster }}' \
            --network-configuration 'awsvpcConfiguration=${{ env.vpc_configuration }}' \
            --task-definition '${{ env.task_definition}}' \
            --overrides '{"containerOverrides": [{"name": "web", "command": ["php", "artisan", "migrate"] }]}' \
            --query tasks[0].taskArn --output text)

以上の内容をGithub Actionsに落とすと冒頭のようなworkflowになります。

その他検討した方法

専用のtaskを定義する

CMDのみが異なるサーバーを起動しているタスクとほぼ同一のタスクを定義する必要があり冗長なので却下しました。

ENTRYPOINT

CMDではなくENTRYPOINTであればコンテナ立ち上げ時に複数命令を実行可能。ただフォアグラウンド命令の前にDBマイグレーションの実行を挟むことになるので、ワンショットにはならず柔軟性に欠けるため却下しました。

execute-command

execute-command[2]でecsタスクに対してコマンドを実行できるようになったのでそれを利用する方法です。やっていることはCMDをoverrideしてrun-taskすることと変わらないはずなので今のところ要件は満たせると思っています。

aws ecs execute-command \
--cluster '${{ env.cluster }}'  \
--task '${{ env.task_definition}}' \
--container web \
--interactive \
--command "php artisan migrate"

amazon-ecs-deploy-task-definition

通常のタスクのデプロイに使っているaws公式のworkflowを利用する方法。現在はone-offタスクの実行は未サポートらしい[3]、 上述の他の方法で対応可能なので今後もサポートはされない気がしています。

ref

[1] run-task — AWS CLI 1.25.58 Command Reference

[2] デバッグ用にAmazon ECS Exec を使用 - Amazon ECS

[3] run one-off task (e.g. migrations) · Issue #54 · aws-actions/amazon-ecs-deploy-task-definition · GitHub

英語の勉強を始める

新年度から転職して英語を使う環境で働き始めた、 仕事で不自由があるといけないので本腰入れて勉強しようと思う。

ちなみに英語力は10年以上前にTOEIC800ちょっと取った程度です。

スピーキング

当初の想定どおりではあるが全然話せない。課題意識としては3つある。 - アウトプットする速度 アウトプットする過程に頭の中で文章を組み立てる作業があるので、あらかじめ喋る内容を決めていないものについてはなかなか言葉が出てこない。使いやすいフレーズを徐々に増やしていくしかないのかなと思って暗記ゲーに取り組もうとしているが正しいのか不明。 - 文法 主語が抜けるとか5W1Hが適切でないとか時制が正しくないとか冠詞が抜けるとかそういう類のもの。これについては転職して2日である程度改善している気がするので様子見。 - 苦手意識 喋るのを躊躇ってしまう現象の原因。バイネームで話を振られたら無理やり喋るんだけどそうでない場合はどうしても躊躇ってしまう。場数を踏んで慣れるしかないかなと思っている。

リスニング

リスニングはそこまで苦手意識はないつもりだったんだが、厳し目に振り返ってみると日常会話だと7割くらい、オンボーディングのセッションだと5割くらいしか理解できていない気がする。特にインド英語のリスニングが壊滅的で3割くらいしか理解できない。ネイティブの人もたまにsorry?と聞き返しているので自分にはなおさらきついんだろうけどインド人比率が高いのでどうにかしたいところではある。インド英語最適はしたくないのでリスニング力の底上げで対応したいところではあるが悩ましい。

以上、まとまりのない感想記事でした。

Androidや研究の話もちょっと落ち着いたら再開します。

LiveDataのこれまで

Coroutinesのアップデートが盛んに行われており、昨今のAndroid界隈でLiveDataからFlowへ乗り換える機運が高まっているように感じます。今一度Flowへの乗り換えをスムーズに行うためにもLiveDataが発表された経緯など歴史を振り返りたいと思います。

Architecture components - introduction (Google I/O '17)

Google I/O 17ではArchitecture Componentsが発表された。LifecycleOwner/LiveData/ViewModelあたりのComponentはこの発表が初出。onStartやonStopなどActivity/Fragmentが持つ複雑なライフサイクルに対応すること、複数のライフサイクルでリソースをシェアすることを目的にLifecycle AwareなLiveDataが発表された。 www.youtube.com

Fun with LiveData (Android Dev Summit '18)

LiveDataについてIt's desgined for a very specific use case と言及されている。LiveDataを使わないケースとして以下3つがあげられていた。

1. 多くのオペレータやストリームが必要な場合 => Rx
2. データの操作にUIやlifecycleが絡まない場合 => callback
3. データの更新がないone-shotな操作 => coroutines

Google I/O 17で言及されていた通りでAndroid固有の複雑なライフサイクルに対応するという狭い目的でLiveDataが発表されたことが強調されていた。ストリーム処理の用途でLiveDataを利用したいという要望をよく見かけるがそもそも用途が異なるということがわかる。

またmap/switchMap/MediatorLiveDataあたりはこの発表が初出。

www.youtube.com

Android Jetpack: What's new in Architecture Components (Google I/O '18)

Android Jetpackが発表された。LiveDataのDataBindingはこの発表が初出。

www.youtube.com

LiveData with Coroutines and Flow (Android Dev Summit '19)

LiveDataとCoroutineの連携について発表された。主題はFragment,Activity,ViewModel,Applicationそれぞれが持つスコープでいかに操作をキャンセルするかであり、Coroutineのスコープについてがメイントピックになっていた。LiveDataはリアクティブストリームビルダーとして設計されたわけではないとここでも強調されており、 実装についてはViewModel,Repository,DataSourceの3層でLiveData,Flowそれぞれを利用した場合のPro/Conが述べられている。特にデータの操作が複雑になりやすいRepository層DataSource層でCoroutine Flowが有効であり、ViewModel層では画面ロード時に1度だけ呼び出すようなone-shotな操作にはLiveData Builderを用い、複数回データが更新されるような処理の場合はRepository層ではFlowを採用したほうが良いと言われていた。

www.youtube.com

まとめ

  • LiveDataの用途は限定的であり、特徴はLifecycle Awareである
  • 複雑なストリーム処理用途ではないためRxにあるようなオペレータは実装されていない
  • RxがオーバースペックなアプリではLiveDataで仕様を実現できたが本来Rxを代替するものではない
  • Coroutine FlowはRxを代替するものだがLiveDataを代替するものではない

kotlinx-coroutines-testのrunBlockingTestについて

前回に引き続きCoroutinesの単体テストの話です。

kotlinx-coroutines-testのDispatchers.setMainについて - sky’s 雑記

今回はrunBlockingTest[1]について取り扱いたいと思います。

⚠理解が曖昧な状態で記述している可能性があります、間違いがあれば訂正お願いします。

環境

implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.20"

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9"

runBlockingTestとは

kotlinx-coroutines-testに含まれるAPIでrunBlocking同様に現在のスレッドをブロックしてコルーチンを起動することが可能。

上記例ではrunBlockingに渡しているブロックで2度コルーチンが起動されておりそれぞれで10secディレイを入れているため以下のような出力になる。

次にrunBlockingをrunBlockingTestに変更した場合

delayが即時実行され処理が完了していることがわかる。

runBlockingTestの用途

runBlockingTestが導入された背景はTesting Coroutines on Android (Android Dev Summit '19)[2]とKotlinConf 2019: Testing with Coroutines by Sean McQuillan[3]が詳しい。Android Dev Summitでは良い単体テストは速く(fast)、信頼性があり(reliable)、独立している(isolated)と言われており、runBlockingTestは特に速さと信頼性の観点で導入されたように思う。

ではrunBlockingTestを利用したいのはどういう場合だろうか。

普段ViewModelが呼び出すRepositoryのsuspend関数は実行結果をmockすることが多くあまりピンと来なかったが、KotlinConfのプレゼンテーションではsuspend関数のタイムアウト処理をテストするケースが例として挙げられていた。例えば以下のようなタイムアウトが設定されたsuspend関数があった場合、これをrunBlockingで実行すると5秒待つ必要がある。

一方同じ処理をrunBlockingTestで書くと以下のようになり即座にテストが通る、またDelayControllerによりCoroutineDispatcherのvirtual timeを変更することでtimeOutが絡むテストが容易に記述できる。

補足

ここまでrunBlockingTestの用途について書いてみたが現状ExperimentalCoroutinesApiであることに加えて、いくつかクリティカルに思えるissueが上がっていたため導入については要検討という印象である。続報に期待したい。

AbstractMethodError when use withTimeout

AbstractMethodError when use withTimeout · Issue #2307 · Kotlin/kotlinx.coroutines · GitHub

2021/01/18時点で最新版1.4.2のkotlinx-coroutines-testを利用すると発生するエラー

Provided example test for withTimeout fails

Provided example test for withTimeout fails · Issue #1390 · Kotlin/kotlinx.coroutines · GitHub

ReadMeのExample[5]をそのまま書くと発生するエラー

Replace TimeoutCancellationException with TimeoutException

上述のsuspend fun foo でTimeoutExceptionをそのままthrowせずにtry/catchしてFooExceptionを投げ直している理由。TimeoutCancellationException is CancellationException, thus is never reported. とのこと。

Replace TimeoutCancellationException with TimeoutException · Issue #1374 · Kotlin/kotlinx.coroutines · GitHub

Use Kotlin Coroutines in your Android App

Use Kotlin Coroutines in your Android App  |  Android Developers

runBlockingTest is experimental, and currently has a bug that makes it fail the test if a coroutine switches to a dispatcher that executes a coroutine on another thread. The final stable is not expected to have this bug.

参考

[1] runBlockingTest - kotlinx-coroutines-test

[2]

www.youtube.com

[3]

www.youtube.com

[4] kotlinx.coroutines/kotlinx-coroutines-test at master · Kotlin/kotlinx.coroutines · GitHub

kotlinx-coroutines-testのDispatchers.setMainについて

Coroutinesの単体テストの話です、マルチスレッドな関数の単体テストを書いていると理解が曖昧なところがまだあるので改めて調べることにします。趣旨としてはCoroutineのテストを書くときに@BeforeでDispatchers.setMainを何故やる必要があるのかと何が行われているのかです。長くなりそうなので一旦記事にします。

TL;DL

  • Androidコンテキストを排除してテストをするためにsetMainでDispatcherを差し替える必要がある
  • 利用するMainDispatcherはloadPriorityで決まる

環境

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2"

背景

CoroutineのDispatchers.Mainは内部的にgetMainLooper()を呼び出している[1]。Looperはandroid.osパッケージに含まれており、UIスレッドで起動することを考えれば当たり前ではあるのだが、つまるところ単体テストではDispatchers.Mainは使えない。なのでviewModelScopelaunch(context = Dispatchers.Main)のようにCoroutineを起動する処理のテストでは何らかの方法でDispatchers.Mainを差し替える必要がある。この問題に対処するためにkotlinx-coroutines-testではDispatchers.Mainの中身を差し替えられるようになっている。

setMain

setMainで行う処理は以下のようになっておりシンプル

TestDispatchers.kt[2]

@ExperimentalCoroutinesApi
public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) {
    require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" }
    val mainDispatcher = Dispatchers.Main
    require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." }
    mainDispatcher.setDispatcher(dispatcher)
}

mainDispatcherに対してTestMainDispatcherの型チェックが入っている、なのでテストではTestDispatcherを使っていることになるのだが、mainDispatcherの型がMainCoroutineDispatcherにしか見えないのでまずはここを追いかけることにする。

Dispatchers.kt[3]

 @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

MainDispatcherLoader.kt[4]

 val factories = if (FAST_SERVICE_LOADER_ENABLED) {
                FastServiceLoader.loadMainDispatcherFactory()
            } else {
                // We are explicitly using the
                // `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
                // form of the ServiceLoader call to enable R8 optimization when compiled on Android.
                ServiceLoader.load(
                        MainDispatcherFactory::class.java,
                        MainDispatcherFactory::class.java.classLoader
                ).iterator().asSequence().toList()
            }

FastServiceLoader.kt[5]

private fun <S> load(service: Class<S>, loader: ClassLoader): List<S> {
        return try {
            loadProviders(service, loader)
        } catch (e: Throwable) {
            // Fallback to default service loader
            ServiceLoader.load(service, loader).toList()
        }
    }

MainDispatcherLoader.kt[4]

            factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)

ということでloadPriorityが最大のMainDispatcherFactoryでMainCoroutineDispatcherを生成することになる。factoriesの中身はFastServiceLoaderで設定されていてTestMainDispatcherFactoryAndroidDispatcherFactory である。それぞれのpriorityは

TestMainDispatcherFactory[6]

internal class TestMainDispatcherFactory : MainDispatcherFactory {
    override val loadPriority: Int
        get() = Int.MAX_VALUE
}

AndroidDispatcherFactory[1]

internal class AndroidDispatcherFactory : MainDispatcherFactory {
    override val loadPriority: Int
        get() = Int.MAX_VALUE / 2
}

となっておりテスト時にはTestMainDispatcherが使用される。

参考

[1] HandlerDispatcher

[2] TestDispatchers

[3] Dispatchers

[4] MainDispatcherLoader

[5] FastServiceLoader

[6] TestMainDispatcherFactory