8年前のMacBook Air(Intel)で全テスト済み。7本のMacアプリをソロ開発した実体験から書いています。案件でも広告でもありません。
順次ファイル転送は遅い。HiyokoAutoSyncは同時実行数を制限した並列転送を使っている。その実装を紹介する。
順次転送の問題
Androidから100枚の写真を1枚ずつMacに転送すると、前の転送が終わるまで次が待たされる。ファイルごとのADBオーバーヘッドが積み重なる。大量のライブラリだと数分かかる。
並列転送は利用可能な帯域をより効率的に使える。同じ100ファイルを6並列で転送すると、体感できるほど速くなる。
tokio::sync::Semaphoreで同時実行数を制御
無制限の並列化はむしろ逆効果——ADB接続とデバイスを圧迫する。セマフォで同時転送数を適切な数に制限する:
use tokio::sync::Semaphore;
use std::sync::Arc;
const MAX_CONCURRENT: usize = 6;
async fn transfer_files(files: Vec<FileEntry>) -> Result<(), AppError> {
let semaphore = Arc::new(Semaphore::new(MAX_CONCURRENT));
let mut handles = vec![];
for file in files {
let sem = Arc::clone(&semaphore);
let handle = tokio::spawn(async move {
let _permit = sem.acquire().await.unwrap();
transfer_single_file(&file).await
});
handles.push(handle);
}
// 結果を収集
for handle in handles {
handle.await??;
}
Ok(())
}
テストした結果、6並列がスイートスポットだった——接続を圧迫せずに速い。
並列転送のプログレス追跡
各転送が独立して進捗を報告する必要がある。アトミックカウンターを使う:
use std::sync::atomic::{AtomicUsize, Ordering};
let completed = Arc::new(AtomicUsize::new(0));
let total = files.len();
for file in files {
let completed = Arc::clone(&completed);
let handle_clone = app_handle.clone();
tokio::spawn(async move {
let _permit = sem.acquire().await.unwrap();
transfer_single_file(&file).await?;
let done = completed.fetch_add(1, Ordering::Relaxed) + 1;
handle_clone.emit("transfer-progress", Progress {
completed: done,
total,
current_file: file.name.clone(),
}).ok();
Ok::<(), AppError>(())
});
}
並列処理のエラーハンドリング
1つの転送失敗が他の全てを止めてはいけない。エラーを収集して最後にまとめて報告する:
let results: Vec<Result<(), AppError>> = futures::future::join_all(handles)
.await
.into_iter()
.map(|r| r.unwrap_or_else(|e| Err(AppError::Task(e.to_string()))))
.collect();
let errors: Vec<_> = results.into_iter().filter_map(|r| r.err()).collect();
if !errors.is_empty() {
// 部分的な失敗を報告——一部転送済み、一部未転送
}
ファイル転送では「全か無か」より部分的な成功の方がいい。
結果
HiyokoAutoSyncの6並列転送は、大量の写真ライブラリに対して順次転送より体感できるほど速い。セマフォパターンは同時実行数を制限したい並列処理全般に再利用できる。
記事が参考になったら ❤️ もらえると励みになります!
HiyokoAutoSync | X → @hiyoyok