質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

2回答

1337閲覧

Androidでの非同期処理についての質問です。

mikandesu4

総合スコア3

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2022/12/03 11:46

編集2022/12/03 14:11

Android開発を行っていたところ、処理が重い部分がメインスレッドの進行を遅らせているので、非同期処理について勉強しています。

試しにExecutorServiceのSingleThreadExecutor上で処理を実行してみたところ、メインスレッドはスムーズに動くようになりましたが、Executor上では処理が蓄積されていき数秒遅れで実行されている状態です。
理想としては、メインスレッドから受け取った値を別スレッド上で処理し、処理が完了したらメインスレッドに値を返し、再度受け取った値で同じ処理を行う、これの繰り返しといった感じです。

Executorの処理が完了していない状態で、再度同じExecutorが呼ばれると処理が溜まっていくのでしょうか。
ご教授いただければ幸いです。

”追記”

大まかな流れとしては以下の通りです。
メインスレッド上ではUI更新の他にも処理を行っているので、一連の流れをすべてメインスレッド上で行うとそちらに支障が出てきます。
なので、比較的重い計算処理を2つに分割し、計算が完了次第メインスレッドで受け取るという形にしたいです。

以下の構成だと、メインスレッドは良好ですが、Executor上では過去の処理命令が蓄積されていき、時間が経つにつれて遅延が発生する状態になっています。
理想としては、Executor1, Executor2のそれぞれにおいて、呼び出し時に空き状態であれば最新のセンサーの値を受け取り計算を実行、実行状態であれば命令を無視できるようにしたいです。(Executor1とExecutor2の計算内容は別物です)

理解が浅いので初歩的な間違いがあれば申し訳ないです。

private var sensor1: Float = null ・・・センサー1の値を格納 private var sensor2: Float = null ・・・センサー2の値を格納 private var result1 : Int = null  ・・・計算処理1の結果を格納 private var result2 : Int = null  ・・・計算処理2の結果を格納 override fun onInitialized(){ calcExecutor1 = Executors.newSingleThreadExecutor() calcExecutor2 = Executors.newSingleThreadExecutor() } // ------ここから繰り返し処理-------- ・センサーから値を取得しsensor1,sensor2を更新 ・値を計算処理するためにExecutorに値を渡す calcExecutor1.execute { result1 = calculate1( sensor1 )・・・calculate1ではこの引数をもとに計算を実行 } calcExecutor2.execute { result2 = calculate2( sensor2 )・・・calculate2ではこの引数をもとに計算を実行      } ・result1 , result2がnullでなければ値を処理(軽)した後に画面に表示 // -------ここまで繰り返し処理--------- override fun onDestroyView() { calcExecutor1.shutdown() calcExecutor2.shutdown() }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2022/12/03 12:00

非同期処理の中身は実際に Android 上で何らかの処理というか計算を行ってるのでしょうか? その場合は非同期かどうかに関わらず計算にかかる時間は変わりません。新しい処理を開始する際に前回(というか実行中)の処理が不要になるなら、その処理をキャンセルできるような作りにすれば良いかと。
mikandesu4

2022/12/03 12:14

非同期処理の中身は計算処理のみになります。 ただ、これをメインスレッド上で行うとUIの表示やアプリの挙動等に問題がでてくるので、裏で並行して行いたいと考えております。 非同期処理について理解が浅いため認識が間違っていたら申し訳ないですが、例えば重たい計算処理を2つのパーツに分割し、ExcutorA, ExcutorBに割り振って実行するとします。 1.メインスレッド上では、計測したデータをExcutorA,ExcutorBに渡し続けます。 2.ExcutorA, ExcutorB上では、受け取ったデータを処理し、完了次第結果をメインスレッドに返します。 3.メインスレッドが結果を受け取るとUIに結果を反映します。 この3つの処理を繰り返し行うといったイメージになります。 非同期処理を利用すればメインスレッド上の処理は妨げられずに、かつExecutorAに関しては最新の解析結果が表示されると考えたのですが、間違いでしょうか。 宜しくお願い致します。
mikandesu4

2022/12/03 12:19

ExecutorA,Bでの計算処理は完了が必須で、かつ最新のデータを解析したいので、Executor上での処理が完了の後、最新の計測データをメインスレッド上から受け取るような形にしたいです。
TakaiY

2022/12/03 12:33

コメントへの回答はここではなく、質問を編集して追記するのがいいと思いますよ。
TakaiY

2022/12/03 12:39

> メインスレッドから受け取った値を別スレッド上で処理し、処理が完了したらメインスレッドに値を返し、再度受け取った値で同じ処理を行う こうなっていない理由がわかりません。 言葉ではわからないので、コードの抜粋など提示できませんか?
jimbe

2022/12/03 15:41 編集

> Executor1, Executor2のそれぞれにおいて、呼び出し時に空き状態であれば最新のセンサーの値を受け取り計算を実行、実行状態であれば命令を無視 大抵は処理のリクエストはキューイングされて、処理スレッドが終わり次第キューから取り出して処理するので、最後の「実行状態であれば命令を無視」というのが本件のキモでしょう。 SingleThreadExecutor は計算するスレッドが1つというだけで、リクエストはキューに溜ります。 もしリスエストは常にされ続けるなら、処理スレッドのほうでキューから取り出せるだけ取り出して最後のリクエストだけ処理をするようにするとか、キュー自体がリクエストされたら既存を消してリクエストされたのだけにする=常に1つしかキューイングしないなどを自分で作ることになるかもしれません。
mikandesu4

2022/12/03 16:27

"非同期処理 LIFO" 等で調べているのですが、なかなか良い結果が見つかりません。 本来この様な使い方をするのは間違いなのでしょうか。 現在はExecutorの出入り口にBoolでフラグを立てて、処理が開始(False)、処理が完了(True)といった風にしてやり過ごしています。 非同期処理は難しいのでまだまだ勉強が必要なようです。
jimbe

2022/12/03 16:51 編集

キューの操作をちょっと工夫するだけですので、作るにしても大して面倒なことでもないと思います。 1件しかキューイングしないので LIFO はちょっと違う気がしますけど。 kotlin なら Executor でベタにスレッドを使わなくてもテがありそうですが、そこまで詳しくないので…スイマセン。
mikandesu4

2022/12/03 16:57

あまりにもヒットしないので間違っている気はしていましたが...やはり。 1件だけキューイングですね。 ありがとうございます。
guest

回答2

0

以下のようにするのはどうですかね?

  • result1 , result2がnullでなければ
    • 値を処理(軽)した後に画面に表示
    • 値を計算処理するためにExecutorに値を渡す
      calcExecutor1.execute { ... }
      calcExecutor2.execute { ... }
  • nullなら、何もしない。

という感じで、スレッドでの処理が終っている = result1,2 に値が入っている かどうかで、executeを実施するかどうかの判定をすればいいのではないかと。
ただ、 thread1 と thread2 が同時に処理が終るわけではないでしょうから、そのあたりの考慮は必要でしょう。

投稿2022/12/04 02:28

TakaiY

総合スコア12765

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

停止も無いやっつけでしかも java で申し訳ないですが、こんな感じは如何でしょう。
勉強中ということで基本と思われる wait - notify です。
process が終わるまでに何度ボタンを押しても、最後のが次に実行されます。

MainActivity.java

java

1import androidx.appcompat.app.AppCompatActivity; 2 3import android.os.*; 4import android.view.View; 5import android.widget.*; 6 7import java.time.LocalTime; 8import java.util.Random; 9 10public class MainActivity extends AppCompatActivity { 11 private Handler handler; 12 private TextView textView; 13 private ScrollView scrollView; 14 15 @Override 16 protected void onCreate(Bundle savedInstanceState) { 17 super.onCreate(savedInstanceState); 18 setContentView(R.layout.activity_main); 19 20 handler = new Handler(Looper.getMainLooper()); 21 textView = findViewById(R.id.textView); 22 scrollView = findViewById(R.id.scrollView); 23 24 LastExecutor lastExecutor = new LastExecutor(); 25 26 Button button = findViewById(R.id.requestButton); 27 button.setOnClickListener(new View.OnClickListener() { 28 Random random = new Random(); 29 int count = 0; 30 @Override 31 public void onClick(View v) { 32 int c = count ++; 33 log("push Button(" + c + ")."); 34 35 lastExecutor.request(() -> { 36 log("process(" + c + ") start."); 37 try { 38 Thread.sleep(random.nextInt(3000) + 2000); //2~5秒 39 } catch(InterruptedException ignore) { 40 } 41 log("process(" + c + ") end."); 42 }); 43 } 44 }); 45 } 46 private void log(String text) { 47 handler.post(() -> { 48 textView.append(LocalTime.now() + ": " + text + "\n"); 49 scrollView.fullScroll(ScrollView.FOCUS_DOWN); 50 }); 51 } 52} 53 54class LastExecutor { 55 private Runnable queue; 56 private Thread worker; 57 private Object[] lock = new Object[0]; 58 59 void request(Runnable process) { 60 synchronized(lock) { 61 queue = process; 62 lock.notifyAll(); 63 } 64 if(worker == null) startWorker(); 65 } 66 private void startWorker() { 67 worker = new Thread(() -> { 68 try { 69 while(true) { 70 Runnable p; 71 synchronized(lock) { 72 while(queue == null) lock.wait(); 73 p = queue; 74 queue = null; 75 } 76 p.run(); 77 } 78 } catch(InterruptedException e) { 79 e.printStackTrace(); 80 } 81 }); 82 worker.start(); 83 } 84}

res/layout/activity_main.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context=".MainActivity"> 8 9 <Button 10 android:id="@+id/requestButton" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" 13 android:text="request" 14 app:layout_constraintEnd_toEndOf="parent" 15 app:layout_constraintStart_toStartOf="parent" 16 app:layout_constraintTop_toTopOf="parent" /> 17 <ScrollView 18 android:id="@+id/scrollView" 19 android:layout_width="0dp" 20 android:layout_height="0dp" 21 app:layout_constraintBottom_toBottomOf="parent" 22 app:layout_constraintEnd_toEndOf="parent" 23 app:layout_constraintStart_toStartOf="parent" 24 app:layout_constraintTop_toBottomOf="@id/requestButton"> 25 <TextView 26 android:id="@+id/textView" 27 android:layout_width="match_parent" 28 android:layout_height="wrap_content" 29 android:textAlignment="textStart" 30 android:textSize="20dp" /> 31 </ScrollView> 32</androidx.constraintlayout.widget.ConstraintLayout>

実行してボタンを適当に押した後のスクリーンショット


私も AndroidStudio に kotlin 変換してもらって修正したらこんな感じになりました。
こちらはそれこそ参考ということで…。

MainActivity.kt

kotlin

1import android.os.Bundle 2import android.os.Handler 3import android.os.Looper 4import android.view.View 5import android.widget.Button 6import android.widget.ScrollView 7import android.widget.TextView 8import androidx.appcompat.app.AppCompatActivity 9import java.time.LocalTime 10import java.util.* 11 12class MainActivity : AppCompatActivity() { 13 private lateinit var handler: Handler 14 private lateinit var textView: TextView 15 private lateinit var scrollView: ScrollView 16 17 override fun onCreate(savedInstanceState: Bundle?) { 18 super.onCreate(savedInstanceState) 19 setContentView(R.layout.activity_main) 20 21 handler = Handler(Looper.getMainLooper()) 22 textView = findViewById(R.id.textView) 23 scrollView = findViewById(R.id.scrollView) 24 25 val lastExecutor = LastExecutor() 26 27 val button: Button = findViewById(R.id.requestButton) 28 button.setOnClickListener(object : View.OnClickListener { 29 val random = Random() 30 var count = 0 31 override fun onClick(v: View) { 32 val c = count++ 33 log("push Button($c).") 34 35 lastExecutor.request { 36 log("process($c) start.") 37 try { 38 Thread.sleep((random.nextInt(3000) + 2000).toLong()) //2~5秒 39 } catch (_: InterruptedException) { 40 } 41 log("process($c) end.") 42 } 43 } 44 }) 45 } 46 private fun log(text: String) { 47 handler.post { 48 textView.append("${LocalTime.now()}: $text\n") 49 scrollView.fullScroll(ScrollView.FOCUS_DOWN) 50 } 51 } 52} 53 54class LastExecutor { 55 private var queue: Runnable? = null 56 private var worker: Thread? = null 57 private val lock = Object() 58 59 fun request(process: Runnable?) { 60 synchronized(lock) { 61 queue = process 62 lock.notifyAll() 63 } 64 if (worker == null) startWorker() 65 } 66 67 private fun startWorker() { 68 worker = Thread { 69 try { 70 while (true) { 71 var p: Runnable 72 synchronized(lock) { 73 while (queue == null) lock.wait() 74 p = queue as Runnable 75 queue = null 76 } 77 p.run() 78 } 79 } catch (e: InterruptedException) { 80 e.printStackTrace() 81 } 82 } 83 worker!!.start() 84 } 85}

投稿2022/12/03 18:34

編集2022/12/04 04:21
jimbe

総合スコア12648

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mikandesu4

2022/12/04 03:26

とても助かります。 コードはAndroidStudioの方でKotlinに変換してくれたので、大方理解することができました。 数か所書き換えるだけで私の環境に合わせることができました。 ありがとうございました。
jimbe

2022/12/04 04:14

私も AndroidStudio に kotlin に変換して貰ったので一応追記しました。 動いて良かったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問