AsyncTask 作为 kotlin 协程

Ben*_*aci 12 multithreading android android-asynctask kotlin kotlin-coroutines

AsyncTask 的典型用途:我想在另一个线程中运行一个任务,在该任务完成后,我想在我的 UI 线程中执行一些操作,即隐藏进度条。

任务将开始,TextureView.SurfaceTextureListener.onSurfaceTextureAvailable完成后我想隐藏进度条。同步执行此操作不起作用,因为它会阻止构建 UI 的线程,使屏幕变黑,甚至不显示我之后想隐藏的进度条。

到目前为止,我使用这个:

inner class MyTask : AsyncTask<ProgressBar, Void, ProgressBar>() {
    override fun doInBackground(vararg params: ProgressBar?) : ProgressBar {
        // do async
        return params[0]!!
    }

    override fun onPostExecute(result: ProgressBar?) {
        super.onPostExecute(result)
        result?.visibility = View.GONE
    }
}
Run Code Online (Sandbox Code Playgroud)

但是这些课程非常丑陋,所以我想摆脱它们。我想用 kotlin 协程来做到这一点。我尝试了一些变体,但它们似乎都不起作用。我最有可能怀疑的工作是这样的:

runBlocking {
        // do async
}
progressBar.visibility = View.GONE
Run Code Online (Sandbox Code Playgroud)

但这不能正常工作。据我了解,它runBlocking不会像那样启动一个新线程AsyncTask,这正是我需要它做的。但是使用thread协程,我没有看到在完成时得到通知的合理方法。另外,我也不能放入progressBar.visibility = View.GONE新线程,因为只有UI线程才允许进行此类操作。

我是协程的新手,所以我不太明白我在这里缺少什么。

Ser*_*gey 20

要使用协程,您需要做几件事:

  • 实现CoroutineScope接口。
  • JobCoroutineContext实例的引用。
  • 当调用在后台线程中运行代码的函数时,使用挂起函数修饰符挂起协程而不阻塞线程
  • 使用withContext(Dispatchers.IO)函数在后台线程中运行代码并使用启动函数来启动协程。

通常我为此使用一个单独的类,例如"Presenter" 或 "ViewModel"

class Presenter : CoroutineScope {
    private var job: Job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job // to run code in Main(UI) Thread

    // call this method to cancel a coroutine when you don't need it anymore,
    // e.g. when user closes the screen
    fun cancel() {
        job.cancel()
    }

    fun execute() = launch {
        onPreExecute()
        val result = doInBackground() // runs in background thread without blocking the Main Thread
        onPostExecute(result)
    }

    private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
        // do async work
        delay(1000) // simulate async work
        return@withContext "SomeResult"
    }

    // Runs on the Main(UI) Thread
    private fun onPreExecute() {
        // show progress
    }

    // Runs on the Main(UI) Thread
    private fun onPostExecute(result: String) {
        // hide progress
    }
}
Run Code Online (Sandbox Code Playgroud)

使用ViewModel以下代码更简洁viewModelScope

class MyViewModel : ViewModel() {
    
    fun execute() = viewModelScope.launch {
        onPreExecute()
        val result = doInBackground() // runs in background thread without blocking the Main Thread
        onPostExecute(result)
    }

    private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
        // do async work
        delay(1000) // simulate async work
        return@withContext "SomeResult"
    }

    // Runs on the Main(UI) Thread
    private fun onPreExecute() {
        // show progress
    }

    // Runs on the Main(UI) Thread
    private fun onPostExecute(result: String) {
        // hide progress
    }
}
Run Code Online (Sandbox Code Playgroud)

要使用viewModelScope将下一行添加到应用程序的build.gradle文件的依赖项:

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
Run Code Online (Sandbox Code Playgroud)

在撰写本文时 final LIFECYCLE_VERSION = "2.3.0-alpha04"


小智 5

您可以让 ProgressBar 在 UI 主线程上运行,同时使用协程异步运行您的任务。

在你的覆盖 fun onCreate() 方法中,

GlobalScope.launch(Dispatchers.Main) { // Coroutine Dispatcher confined to Main UI Thread
    yourTask() // your task implementation
}
Run Code Online (Sandbox Code Playgroud)

你可以初始化,

private var jobStart: Job? = null
Run Code Online (Sandbox Code Playgroud)

在 Kotlin 中,var 声明意味着属性是可变的。如果将其声明为 val,则它是不可变的、只读的且无法重新分配。

在 onCreate() 方法之外, yourTask() 可以实现为一个挂起函数,它不会阻塞主调用者线程。

当函数在等待返回结果时被挂起时,它的运行线程会被解除阻塞以供其他函数执行。

private suspend fun yourTask() = withContext(Dispatchers.Default){ // with a given coroutine context
    jobStart = launch {
       try{
        // your task implementation
       } catch (e: Exception) {
             throw RuntimeException("To catch any exception thrown for yourTask", e)
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

对于您的进度条,您可以创建一个按钮以在单击该按钮时显示进度条。

buttonRecognize!!.setOnClickListener {
    trackProgress(false)
}
Run Code Online (Sandbox Code Playgroud)

在 onCreate() 之外,

private fun trackProgress(isCompleted:Boolean) {
    buttonRecognize?.isEnabled = isCompleted // ?. safe call
    buttonRecognize!!.isEnabled // !! non-null asserted call

    if(isCompleted) {
        loading_progress_bar.visibility = View.GONE
    } else {
        loading_progress_bar.visibility = View.VISIBLE
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个提示是检查您的协程是否确实在另一个线程上运行,例如。DefaultDispatcher-worker-1,

Log.e("yourTask", "Running on thread ${Thread.currentThread().name}")
Run Code Online (Sandbox Code Playgroud)

希望这是有帮助的。


Ser*_*gey 5

另一种方法是在 上创建通用扩展函数CoroutineScope

fun <R> CoroutineScope.executeAsyncTask(
        onPreExecute: () -> Unit,
        doInBackground: () -> R,
        onPostExecute: (R) -> Unit
) = launch {
    onPreExecute()
    val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
        doInBackground()
    }
    onPostExecute(result)
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以将它与 any 一起使用CoroutineScope

  • ViewModel

      class MyViewModel : ViewModel() {
    
          fun someFun() {
              viewModelScope.executeAsyncTask(onPreExecute = {
                  // ...
              }, doInBackground = {
                  // ...
                  "Result" // send data to "onPostExecute"
              }, onPostExecute = {
                  // ... here "it" is a data returned from "doInBackground"
              })
          }
      }
    
    Run Code Online (Sandbox Code Playgroud)
  • ActivityFragment

      lifecycleScope.executeAsyncTask(onPreExecute = {
          // ...
      }, doInBackground = {
          // ...
          "Result" // send data to "onPostExecute"
      }, onPostExecute = {
          // ... here "it" is a data returned from "doInBackground"
      })
    
    Run Code Online (Sandbox Code Playgroud)

要在应用程序的build.gradle文件的依赖项中使用viewModelScopelifecycleScope添加下一行:

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope
Run Code Online (Sandbox Code Playgroud)

在撰写本文时final LIFECYCLE_VERSION = "2.3.0-alpha05"

  • 我喜欢你!我想我在读完这段代码后才真正理解了协程 (2认同)