結果
- 2 〜 4 並列(
WorkManager.getInstance()
で取得した場合) - これは、
AsyncTask#THREAD_POOL_EXECUTOR
と同じ(端末依存)
※ android.arch.work:work-runtime:1.0.0-alpha02
時点の記事です
↓ 2019/08/25追記 ↓
Coroutine を導入・導入検討されている方は、CoroutineWorker は何並列で実行されるのか? もご覧下さい。
↑ 2019/08/25追記 ↑
背景
サーバへの送信処理を、Google I/O 2018 で発表された WorkManager によるバックグラウンド実行の仕組に置き換えようとしていました。
複数の送信処理を 並列化して高速化する という要件もあり、何並列で実行されるのかを調査しました。
(一気に積んだ数だけ Thread が立ったら、サーバに負荷がかかる)
まずは試したコードと結果
コード
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val workManager = WorkManager.getInstance()
fab.setOnClickListener {
// 一気に10個積む
for (i in 1..10) {
val request = OneTimeWorkRequest.Builder(MyWorker::class.java).build()
workManager.enqueue(request)
Log.d("★", "enqueue:$i")
}
}
}
class MyWorker : Worker() {
override fun doWork(): WorkerResult {
Log.d("★", Thread.currentThread().name) // スレッド名を表示
Thread.sleep(3000) // 3秒かかる処理に見立てる
return WorkerResult.SUCCESS
}
}
}
結果
(中略) D/★: enqueue:1
(中略) D/★: enqueue:2
(中略) D/★: enqueue:3
(中略) D/★: enqueue:4
(中略) D/★: enqueue:5
(中略) D/★: enqueue:6
(中略) D/★: enqueue:7
(中略) D/★: enqueue:8
(中略) D/★: enqueue:9
(中略) D/★: enqueue:10
(中略) D/★: pool-1-thread-1
(中略) D/★: pool-1-thread-2
(中略) D/★: pool-1-thread-3
約3秒...
(中略) D/★: pool-1-thread-1
(中略) D/★: pool-1-thread-2
(中略) D/★: pool-1-thread-3
約3秒...
(中略) D/★: pool-1-thread-1
(中略) D/★: pool-1-thread-2
(中略) D/★: pool-1-thread-3
約3秒...
(中略) D/★: pool-1-thread-1
3並列の動作になってますね(@Nexus 5X API 26 Emulator)
一気に10個の処理を enqueue しても、3並行で処理されているのがわかります。
(Producer-Consumer パターンですね)
根拠
では、なぜ3並列で実行されたのかを見ていきます。
MyWorker#doWork の呼び出し元
public void run() {
...
Worker.WorkerResult result;
try {
result = mWorker.doWork(); // ← ここ
} catch (Exception | Error e) {
result = Worker.WorkerResult.FAILURE;
}
...
}
WorkerWrapper の生成と実行
public synchronized boolean startWork(...){
...
// 生成
WorkerWrapper workWrapper = new WorkerWrapper.Builder(mAppContext, mWorkDatabase, id)
.withListener(this)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
mEnqueuedWorkMap.put(id, workWrapper);
// 実行
mExecutor.execute(workWrapper);
...
}
WorkerWrapper#mExecutor の生成場所
public final class Configuration
private Configuration(@NonNull Configuration.Builder builder) {
if (builder.mExecutor == null) {
mExecutor = createDefaultExecutor(); // WorkManager.getInstance() だとこっち
} else {
mExecutor = builder.mExecutor;
}
}
...
// ↓肝がこれ
private Executor createDefaultExecutor() {
return Executors.newFixedThreadPool(
// This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR.
Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)));
}
Configuration.Builder
に mExecutor
を指定してやれば、並列数をカスタマイズできそうな雰囲気を感じ取れますね。(今回は触れないことにします)
ちなみに、 AsyncTask のコード
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
→ CPU 数を見て、何並行で実行するのかを決めていることがわかります。
まとめ
WorkManager に enqueue した Work は何並列で実行されるのか?
- 2 〜 4 並列(
WorkManager.getInstance()
で取得した場合) - これは、
AsyncTask#THREAD_POOL_EXECUTOR
と同じ(端末依存)