美文网首页Kotlin
Kotlin协程使用

Kotlin协程使用

作者: syimo | 来源:发表于2020-04-17 17:04 被阅读0次

    目录

    • 协程的用法
    • 协程同步异步请求与Rxjava,原生写法的区别与优势
    • 对于协程的理解

    1.协程的用法

    在安卓中添加引用,引用里面也包括了协程核心库

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
    

    接下来我们做三个网络请求实验,fetchData()方法是一个网络请求,handleDataOnUI()方法是一个主线程UI方法。

    1.这里我们把方法放在GlobalScope.launch当中,指定Dispatchers.Main,即里面的代码将在主线程中执行
    2.我们准备了三个网络请求,api1 api2 api3, 其中api2是一个耗时的网络请求,用来区分是否网络请求是有序发出还是并发
    3.为async,withContext的方法指定了不同的Dispatchers.IO线程

            //实验1,通过log日志可以看出,这三个网络请求是有序的,一个完成在请求另一个,等待全部完成执行最后
            GlobalScope.launch(Dispatchers.Main) {
    
                val data1 = async(Dispatchers.IO) { fetchData(1, api1) }.await()
                val data2 = async(Dispatchers.IO) { fetchData(2, api2) }.await()
                val data3 = async(Dispatchers.IO) { fetchData(3, api3) }.await()
    
                handleDataOnUI(data1!!, data2!!, data3!!)
    
            }
    
    实验一.png
          //实验2,通过log日志可以看出,这三个网络请求也是有序的
            GlobalScope.launch(Dispatchers.Main) {
    
                val data1 = withContext(Dispatchers.IO) { fetchData(1, api1) }
                val data2 = withContext(Dispatchers.IO) { fetchData(2, api2) }
                val data3 = withContext(Dispatchers.IO) { fetchData(3, api3) }
    
                handleDataOnUI(data1!!, data2!!, data3!!)
            }
    
    实验二.png
            //实验3,通过log日志可以看出,终于这三个网络请求是并发的
            GlobalScope.launch(Dispatchers.Main) {
    
                val data1 = async(Dispatchers.IO) { fetchData(1, api1) }
                val data2 = async(Dispatchers.IO) { fetchData(2, api2) }
                val data3 = async(Dispatchers.IO) { fetchData(3, api3) }
    
                handleDataOnUI(data1.await()!!, data2.await()!!, data3.await()!!)
    
            }
    
    
    实验三.png
    • 通过上面实验可以得出结论,无论网络请求是顺序执行的,还是并发执行的,都是执行完毕所有网络请求以后,才最终去处理handleDataOnUI()的方法。通过协程,就能做到这么简洁的写发。

    原因分析

    • 为什么会造成,不同的写法,一种是有序而另一种方式并发的呢?
      1.首先我们看withContext(),这是一个suspend函数,通过api我们可以看到,当执行这个方法时,会阻塞当前block代码块,也就是下一行withContext()函数不会开始,直到当前方法结束
     * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
     * the result.
    public suspend fun <T> withContext(
        context: CoroutineContext,
        block: suspend CoroutineScope.() -> T
    ): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        //.....
    

    2.接着我们看async函数,它实际上返回的是Deferred对象,并不会阻塞当前block函数。但是Deferred对象有一个await()函数,我们看下具体定义。我们可以看到,这里await()同样是一个susupend函数,等待结果。有点类似于Java当中的Future模式,等待但是并不阻塞,直到结果返回在再次唤醒。

    /**
     * Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete,
     * returning the resulting value or throwing the corresponding exception if the deferred was cancelled.
     *
     * This suspending function is cancellable.
     * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
     * immediately resumes with [CancellationException].
     *
     * This function can be used in [select] invocation with [onAwait] clause.
     * Use [isCompleted] to check for completion of this deferred value without waiting.
     */
    public suspend fun await(): T
    

    3.总而言之,如果想要并发请求,我们必须要用async,如果是顺序执行,我们则可以使用withContext, 没必要使用async

    //并发执行的正确写法
    val task1 = async(Dispatcher.io){ doSomeStuff}
    val task2 = async(Dispatcher.io){ doSomeStuff}
    task1.await()
    task2.await()
    
    //顺序执行的常规写法
    val task1 = withContext(Dispatcher.io){ doSomeStuff}
    val task2 = withContext(Dispatcher.io){ doSomeStuff}
    task1.await()
    task2.await()
    //或者
    val task1 = async(Dispatcher.io){ doSomeStuff}.await()
    val task2 = async(Dispatcher.io){ doSomeStuff}.await()
    val total=  task1+task2
    

    2.协程同步异步请求与Rxjava,原生写法的区别与优势

    • rxjava实现多个有序网络请求,因为rxjava是上游依次往下游发送事件,所以很容易写顺序请求
    Observable.create(object : ObservableOnSubscribe<String> {
                override fun subscribe(emitter: ObservableEmitter<String>?) {
                    emitter!!.onNext("url")
                    emitter!!.onComplete()
                }
            }).map(object : Function<String, String> {
                override fun apply(t: String?): String {
                    val data1 = fetchData(1, api1)
                    return data1!!;
                }
            }).map(object : Function<String, String> {
                override fun apply(t: String?): String {
                    val data2 = fetchData(2, api2)
                    return "[======$t][----$data2]"
                }
    
            }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({ next ->
                    Log.d(TAG, next)
    
                }, { error ->
                    error.printStackTrace()
                })
    
    • rxjava实现多个并发网络请求,我们可以用zip
     Observable.zip(getObservable(1, api1), getObservable(2, api2),
                getObservable(3, api3), object : Function3<String, String, String, String> {
                    override fun apply(t1: String?, t2: String?, t3: String?): String {
    
                        return t1 + t2 + t3
                    }
    
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(object : Observer<String> {
                    override fun onComplete() {
                        Log.d(TAG, "Observer onComplete")
                    }
    
                    override fun onSubscribe(d: Disposable?) {
                        Log.d(TAG, "Observer onSubscribe")
                    }
    
                    override fun onNext(t: String?) {
                        Log.d(TAG, "Observer onNext $t")
                    }
    
                    override fun onError(e: Throwable?) {
                        Log.d(TAG, "Observer onError")
                    }
    
                })
    
    
    • 如果用原生代码实现的话,这里就简单说下思路
      1.顺序请求,不停接口回调即可实现
      2.并发请求,多个线程并发执行,维护一个数组判断每个任务执行状态,在每个线程结束时,以同步的方式去检测和更新数组,如果全部执行完毕,则任务结束。

    总之就是,协程对于多个并发网络请求,确实能够简写代码,方便使用和处理。

    3.对于协程的理解

    协程的用法有很多,我这也是简单的了解了基本的用法。协程更像是一个封装好的多线程请求框架,在安卓里面能更加方便切换线程,又能做到顺序和并发的网络请求,确实大大方便了网络请求的开发书写,但是对于网络请求中的失败,无网络等错误状态。还需要进一步学习和研究。对于协程的核心原理,更多是对底层java 多线程,线程池的封装,也有java Future模式的影子...

    参考

    https://www.kotlincn.net/docs/reference/coroutines/basics.html
    https://stackoverflow.com/questions/52369508/kotlin-coroutines-async-await-sequence
    https://blog.mindorks.com/mastering-kotlin-coroutines-in-android-step-by-step-guide
    concurrent-using-async

    相关文章

      网友评论

        本文标题:Kotlin协程使用

        本文链接:https://www.haomeiwen.com/subject/xghevhtx.html