Reference: www.jianshu.com/u/a324daa6f…
Reference – Throw line: rengwuxian.com/kotlin-coro…
Explore the Kotlin coroutine mechanism
- What is a coroutine
- Use of coroutines
- Start of coroutines
- Coroutine hanging, recovery principle reverse analysis
1. What is coroutine
Scenario 1: Asynchronous callback nesting
- Regular writing
// The client makes three network asynchronous requests in sequence and updates the UI with the final results
request1(parameter) { value1 ->
request2(value1) { value2 ->
request3(value2) { value3 ->
updateUI(value3)
}
}
}
Copy the code
This structure of code is extremely bad to read and maintain. I affectionately refer to nested coupling of multiple callbacks as “callback hell.”
- Writing coroutines
GlobalScope.launch(Dispatchers.Main){
val value1 = request1()
val value2 = request2(value1)
valValue3 = request2(value2) updateUI(value3)}suspend request1( )
suspend request2(..)
suspend request3(..)
Copy the code
Scenario 2: Concurrent flow control
- Regular writing
// The client makes three network asynchronous requests in sequence and updates the UI with the final result
fun request1(parameter) { value1 ->
request2(value1) { value2 ->
this.value2=value2
if(request3){
updateUI()
}
}
request3(value2) { value3 ->
this.value3=value3
if(request2) {
updateUI()
}
}
}
fun updateUI(a)
Copy the code
- Coroutines writing
GlobalScope.launch(Dispatchers.Main){
val value1 = request1()
val deferred2 = GlobalScope.async{request2(value1)}
val deferred3 = GlobalScope.async{request3(value2)}
updateUI(deferred2.await(),deferred3.await())
}
suspend request1( )
suspend request2(..)
suspend request3(..)
Copy the code
The purpose of coroutines is to allow better collaboration between multiple tasks and solve asynchronous callback nesting. Ability to orchestrate code synchronously for asynchronous work. Make asynchronous code as intuitive as synchronous code. It is also a solution for concurrent flow control.
Coroutines are designed to simplify complex code written using “async + callback” into something that looks like synchronous writing, weakening the concept of threads (further abstraction of thread operations).
2. Usage of coroutines
Introduce gradle dependencies
/ / in kotlin project cooperate jetpack architecture introduced coroutines API 'androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.2.0' API 'androidx. Lifecycle: lifecycle - runtime - KTX: 2.2.0' API 'androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0' / / in kotlin project but not the jetpack architecture project introduced coroutines API "org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.2.1" API 'org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.1.1'Copy the code
Common methods for creating coroutines
/ / create coroutines, by Dispatchers. IO, MAIN, Unconfined specified coroutines running threads
val job:Job =GlobalScope.launch(Dispatchers.Main)
valDeffered: deffered = GlobalScope. Async (Dispatchers. IO)Copy the code
Job: The return value of the coroutine builder. Think of Job as the coroutine object itself, containing the control methods for the coroutine.
Deffered is a subclass of Job and actually adds an await method. Can temporarily suspend the current coroutine, pausing further execution. When the await method returns a value, the coroutine is resumed and execution continues
methods | instructions |
---|---|
start() | Start coroutines manually |
join() | Wait for the coroutine to finish executing |
cancel() | Cancel a coroutine |
Start of coroutines
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope. () - >Unit
): Job{
val newContext = newCoroutineContext(context)
val coroutine = StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
}
Copy the code
CoroutineContext – the context of a coroutine, which is a key-value data structure
CoroutineContext | List |
---|---|
get(key: Key): E | get(int index) |
plus(context: Element) | add(int index, E element) |
minusKey(key: Key<*>) | remove(E element) |
A simple example
object CoroutineScene {
private const val TAG = "CoroutineScene"
fun startScene1(a){
GlobalScope.launch (Dispatchers.Main){
Log.i(TAG,"startScene work on ${Thread.currentThread().name}")
val result1 = request1()
val result2 = request2(result1)
val result3 = request3(result2)
updateUI(result3)
}
}
fun startScene2(a){
GlobalScope.launch (Dispatchers.Main){
Log.i(TAG,"startScene work on ${Thread.currentThread().name}")
val result1 = request1()
val deferred2 = GlobalScope.async { request2(result1) }
val deferred3 = GlobalScope.async { request3(result1) }
// Cannot call await alone
updateUI(deferred2.await(),deferred3.await())
}
}
private fun updateUI(result: String) {
Log.i(TAG,"updateUI work on ${Thread.currentThread().name}")
Log.i(TAG,"result: $result")}private fun updateUI(result2:String,result3: String) {
Log.i(TAG,"updateUI work on ${Thread.currentThread().name}")
Log.i(TAG,"result: ${result2}--${result3}")}Is the suspend keyword useful?
// Delay is an asynchronous task. How does delay coroutine execute down?
suspend fun request1(a):String{
delay(2*1000) // A delay of 2 seconds will not suspend the thread, but will suspend the current coroutine
Log.i(TAG,"request1 work on ${Thread.currentThread().name}")
return "result from request1"
}
suspend fun request2(request:String):String{
delay(2*1000) // Delay 2 seconds
Log.i(TAG,"request2 work on ${Thread.currentThread().name}")
return "result from request2"
}
suspend fun request3(request:String):String{
delay(2*1000) // Delay 2 seconds
Log.i(TAG,"request3 work on ${Thread.currentThread().name}")
return "result from request3"}}Copy the code
CoroutineDispatcher a thread scheduler run by a coroutine
Coroutine scheduler
model | instructions |
---|---|
Dispatchers.IO | Displays the thread on which the specified coroutine runs, the IO thread |
Dispatchers.Main | Specifies that the coroutine runs on the main thread |
Dispatchers.Default | By default, a thread is started when Ctrip is started |
Dispatchers.Unconfined | If not specified, the coroutine will run on the current thread, depending on the thread in which the coroutine was suspended |
CoroutineStart – Start mode
The default is DEAFAULT, which is to create on the start; Another is LAZY, which means you call startup when you need it
model | instructions |
---|---|
CoroutineStart().DEAFAULT | Mode mode, create start coroutine, can be cancelled at any time |
ATOMIC | In automatic mode, it can be started immediately after being created, but cannot be cancelled before being started |
LAZY | Lazy start mode, which starts only when the start method is called |
3. Coroutine suspension, recovery principle reverse analysis
Hang up function
Methods decorated by the keyword suspend At compile time, the compiler modifies the signature of the method. Includes return values, modifiers, input parameters, and method body implementations. Coroutines are suspended by suspending code implemented in functions.
//kotlin
suspend fun request(a): String {
delay(2 * 1000) //suspend fun()
println("after delay")
return "result from request"
}
Copy the code
After switching to Java
//java
public static final Object request(Continuation completion) {
ContinuationImpl requestContinuation = completion;
if ((completion.label & Integer.MIN_VALUE) == 0)
requestContinuation = new ContinuationImpl(completion) {
@Override
Object invokeSuspend(Object o) {
label |= Integer.MIN_VALUE;
return request(this); //3. Request again}}; } switch (requestContinuation.label) { case0: { For the first time, execute the method and change the tag to 1
requestContinuation.label = 1;
//2. Execute the delay operation and put the asynchronous listener in it, so we can see that the coroutine is actually a method suspension
Object delay = DelayKt.delay(2000, requestContinuation);
if (delay == COROUTINE_SUSPENDED) {
returnCOROUTINE_SUSPENDED; }}}//4. Execute the following method
System.out.println("after delay")
return "result from request";
}
Copy the code
Coroutine suspension and coroutine recovery
The core of coroutines is suspend —- restore, suspend — the essence of recovery is return & callback callback
Simulate the entire process of suspend and resume
object CoroutineScene2 {
private val TAG :String = "CoroutineScene2"
suspend fun request2(a):String{
delay(2*1000);
Log.i(TAG,"request2 completed")
return "result from request2"; }}Copy the code
Java
public class CoroutineScene2_decompiled {
private static final String TAG = "CoroutineScene2";
//1. Suspend the process
public static final Object request2(Continuation preCallback) {
ContinuationImpl request2Callback;
if(! (preCallbackinstanceof ContinuationImpl) || (((ContinuationImpl) preCallback).label & Integer.MAX_VALUE) == 0) {
request2Callback = new ContinuationImpl(preCallback) {
@Override
public Object invokeSuspend(@NotNull Object resumeResult) {
this.result = resumeResult;
this.label |= Integer.MAX_VALUE;
return request2(this); }}; }else {
request2Callback = (ContinuationImpl) preCallback;
}
switch (request2Callback.label){
case 0:
Object delay = DelayKt.delay(2000,request2Callback);
if(delay == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
return IntrinsicsKt.getCOROUTINE_SUSPENDED();
}
}
Log.i(TAG,"request2 comleted");
return "result from request 2";
}
static abstract class ContinuationImpl<T> implements Continuation<T> {
Continuation preCallback;
int label;
Object result;
public ContinuationImpl(Continuation preCallback) {
this.preCallback = preCallback;
}
@NotNull
@Override
public CoroutineContext getContext(a) {
return preCallback.getContext();
}
//2. Restore process
@Override
public void resumeWith(@NotNull Object result) {
Object suspend = invokeSuspend(result);
if(suspend == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
return;
}
preCallback.resumeWith(suspend);
}
public abstract Object invokeSuspend(@NotNull Object resumeResult); }}Copy the code
kotliin
object CoroutineScene2 {
private val TAG :String = "CoroutineScene2"
suspend fun request1(a):String{
val request2 :String = request2();
return "result from request1" + request2
}
suspend fun request2(a):String{
delay(2*1000);
Log.i(TAG,"request2 completed")
return "result from request2"; }}Copy the code
Java
public class CoroutineScene2_decompiled {
private static final String TAG = "CoroutineScene2";
//1. Suspend the process
public static final Object request1(Continuation preCallback) {
ContinuationImpl request1Callback;
if(! (preCallbackinstanceof ContinuationImpl) || (((ContinuationImpl) preCallback).label & Integer.MAX_VALUE) == 0) {
request1Callback = new ContinuationImpl(preCallback) {
@Override
public Object invokeSuspend(@NotNull Object resumeResult) {
this.result = resumeResult;
this.label |= Integer.MAX_VALUE;
Log.i(TAG,"request1 has resumed");
return request1(this); }}; }else {
request1Callback = (ContinuationImpl) preCallback;
}
switch (request1Callback.label){
case 0:
//Object delay = DelayKt.delay(2000,request2Callback);
Object request2= request2(request1Callback);
if(request2 == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
Log.i(TAG,"request1 has suspended");
return IntrinsicsKt.getCOROUTINE_SUSPENDED();
}
}
Log.i(TAG,"request2 completed");
return "result1 from request1 " + request1Callback.result;
}
//1. Suspend the process
public static final Object request2(Continuation preCallback) {
ContinuationImpl request2Callback;
if(! (preCallbackinstanceof ContinuationImpl) || (((ContinuationImpl) preCallback).label & Integer.MAX_VALUE) == 0) {
request2Callback = new ContinuationImpl(preCallback) {
@Override
public Object invokeSuspend(@NotNull Object resumeResult) {
this.result = resumeResult;
this.label |= Integer.MAX_VALUE;
Log.i(TAG,"request2 has resumed");
return request2(this); }}; }else {
request2Callback = (ContinuationImpl) preCallback;
}
switch (request2Callback.label){
case 0:
Object delay = DelayKt.delay(2000,request2Callback);
if(delay == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
Log.i(TAG,"request2 has suspended");
return IntrinsicsKt.getCOROUTINE_SUSPENDED();
}
}
Log.i(TAG,"request2 comleted");
return "result from request 2";
}
static abstract class ContinuationImpl<T> implements Continuation<T> {
Continuation preCallback;
int label;
Object result;
public ContinuationImpl(Continuation preCallback) {
this.preCallback = preCallback;
}
@NotNull
@Override
public CoroutineContext getContext(a) {
return preCallback.getContext();
}
//2. Restore process
@Override
public void resumeWith(@NotNull Object result) {
Object suspend = invokeSuspend(result);
if(suspend == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
return;
}
preCallback.resumeWith(suspend);
}
public abstract Object invokeSuspend(@NotNull Object resumeResult); }}Copy the code
Call:
val callback = Continuation<String>(Dispatchers.Main){result->
Log.i(TAG,result.getOrNull())
}
CoroutineScene2_decompiled.request1(callback)
Copy the code
Coroutines review
-
What is a coroutine
- Coroutines are a solution, a solution to the concept of nested, concurrent, weak threads. Allows for better collaboration between multiple tasks and the ability to orchestrate code synchronously for asynchronous work. Write asynchronous code as intuitively as synchronous code.
-
Start of coroutines
- According to create coroutines specified scheduler HandlerDispatcher, DefaultScheduler, UnconfinedDispatcher to perform a task, to determine coroutines in blocks of code to run on the thread.
-
Suspend coroutine, resume
- The essence of the method is suspend, resume. The essence is return +callback.
- Callbacks between methods are handled with compile-time transformations, which makes it intuitive to write sequential asynchronous code.
-
Is a coroutine a threading framework?
- The essence of coroutines is return +callback at compile time. It simply provides a scheduler that can run on an IO thread when scheduling tasks.
-
When to use coroutines
- Multi-task concurrent flow control scenario is better to use, the flow control is relatively simple, does not involve thread blocking and wake up, the performance is higher than Java concurrency control means.