コトリンコルーチンでのサスペンド機能の意味


118

私はコトリンコルーチンを読んでいて、それがsuspend機能に基づいていることを知っています。しかし、どういうsuspend意味ですか?

コルーチンまたは関数が中断されますか?

https://kotlinlang.org/docs/reference/coroutines.htmlから

基本的に、コルーチンはスレッドをブロックせずに中断できる計算です

「サスペンド機能」とよく言われます。しかし、関数が終了するのを待っているので中断されるのはコルーチンだと思いますか?「一時停止」は通常「操作を停止する」ことを意味します。この場合、コルーチンはアイドル状態です。

weコルーチンは一時停止されていると言えますか?

どのコルーチンが一時停止されますか?

https://kotlinlang.org/docs/reference/coroutines.htmlから

類推を続けると、await()は、何らかの計算が完了するまでコルーチンを中断してその結果を返す中断関数(したがって、非同期{}ブロック内からも呼び出し可能)にすることができます。

async { // Here I call it the outer async coroutine
    ...
    // Here I call computation the inner coroutine
    val result = computation.await()
    ...
}

🤔「計算が完了するまでコルーチンを一時停止する」とありますが、コルーチンは軽量スレッドのようなものです。したがって、コルーチンが中断されている場合、どのように計算を実行できますか?

私たちは見るawaitに呼び出されたcomputationことがあるかもしれないので、asyncそれを返すDeferredことが別のコルーチンを開始できる手段、

fun computation(): Deferred<Boolean> {
    return async {
        true
    }
}

quote コルーチンを一時停止するという引用。それsuspendは外側のasyncコルーチン、またはsuspend内側のcomputationコルーチンを意味しますか?

suspendながら、外側のことを意味しasyncコルーチンが(待っているawait内側のために)computationそれ(アウター、仕上げにコルーチンasyncコルーチン)スレッドプールには空転(名前の由来は、サスペンド)と戻り、スレッド、および時に子computationコルーチンが終了すると、それは(外asyncコルーチン)ウェイクアップし、プールから別のスレッドを取得して続行しますか?

スレッドについて言及する理由は、https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.htmlが原因です

コルーチンが待機している間にスレッドがプールに返され、待機が完了すると、コルーチンはプール内の空きスレッドで再開します。

回答:


113

一時停止機能は、すべてのコルーチンの中心にあります。一時停止機能は、一時停止して後で再開できる機能です。彼らは長時間実行オペレーションを実行し、ブロックせずに完了するまで待つことができます。

一時停止関数の構文は、suspendキーワードが追加されていることを除いて、通常の関数の構文と似ています。パラメータを取り、戻り値の型を持つことができます。ただし、一時停止関数は、別の一時停止関数またはコルーチン内でのみ呼び出すことができます。

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

内部的には、サスペンド関数は、コンパイラによって、サスペンドキーワードのない別の関数に変換されますContinuation<T>。たとえば、上記の関数は、コンパイラによって次のように変換されます。

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

Continuation<T> コルーチンを再開するために呼び出される2つの関数を含むインターフェースで、戻り値を返すか、関数が中断されているときにエラーが発生した場合は例外を返します。

interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}

4
別の謎が明らかに!すごい!
WindRider 2018

16
この機能は実際にどのように一時停止されているのでしょうか。彼らは常にそれsuspend funを一時停止することができると言いますが、正確にはどうですか?
WindRider 2018

2
@WindRiderこれは、現在のスレッドが他のコルーチンの実行を開始し、後でこのコルーチンに戻ることを意味します。
ジョフリー

2
私は「不思議な」メカニズムを理解しました。それは、Tools> Kotlin> Bytecode> Decompile btnの助けを借りて簡単に明らかにすることができます。これは、いわゆる「一時停止ポイント」がどのように実装されるかを示します-継続などを介して。誰でも自分の目で見てみることができます。
WindRider

4
@buzaa バイトコードレベルで説明する、2017年のRoman Elizarovによる講演です。
Marko Topolnik

30

コルーチンを一時停止することの正確な意味を理解するには、次のコードを実行することをお勧めします。

import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() = runBlocking {
    launch(Unconfined) {
        val a = a()
        println("Result is $a")
    }
    10.downTo(0).forEach {
        continuation!!.resume(it)
    }
}

suspend fun a(): Int {
    return b()
}

suspend fun b(): Int {
    while (true) {
        val i = suspendCoroutine<Int> { cont -> continuation = cont }
        if (i == 0) {
            return 0
        }
    }
}

Unconfinedコルーチンディスパッチャは、魔法の排除コルーチン派遣をし、私たちは裸のコルーチンに直接集中することができます。

launchブロック内のコードは、launch呼び出しの一部として、現在のスレッドですぐに実行を開始します。次のようになります。

  1. 評価する val a = a()
  2. これはに繋がりb()、到達しsuspendCoroutineます。
  3. 関数b()は渡されたブロックを実行しsuspendCoroutine、特別なCOROUTINE_SUSPENDED値を返します。この値はKotlinプログラミングモデルでは監視できませんが、コンパイルされたJavaメソッドが実行することです。
  4. Functionはa()、この戻り値を見て、それ自体も返します。
  5. launchブロックは同じとコントロールは今後の行に戻っんlaunch呼び出し:10.downTo(0)...

この時点で、launchブロック内のfun mainコードとコードが同時に実行されている場合と同じ結果になることに注意してください。これはすべて、単一のネイティブスレッドで発生しているため、launchブロックは「一時停止」されています。

forEachループコード内で、プログラムcontinuationb()関数が書き込んresumesだとの値を使ってそれを読み取ります10resume()suspendCoroutine、渡された値で呼び出しが返されたかのように実装されています。そのため、を実行している最中に突然気づきましたb()。に渡した値は、resume()に割り当てられi、チェックされます0。ゼロでない場合、while (true)ループはの内部b()で続き、再びに達しsuspendCoroutineます。その時点でresume()呼び出しが戻り、ここでの別のループ手順を実行しforEach()ます。これは、最後にで再開するまで続き0、次にprintlnステートメントが実行されてプログラムが完了します。

上記の分析は、「コルーチンの一時停止」がコントロールを最も内側のlaunch呼び出し(または、より一般的には、コルーチンビルダー)に戻すことを意味するという重要な直感を与えるはずです。コルーチンが再開後に再び一時停止resume()すると、呼び出しは終了し、制御はの呼び出し元に戻りますresume()

コルーチンディスパッチャーが存在すると、ほとんどのコードが別のスレッドにすぐにコードを送信するため、この推論の明確性が低下します。その場合、上記のストーリーはその別のスレッドで発生し、コルーチンディスパッチャーもcontinuationオブジェクトを管理するため、戻り値が利用可能になったときにオブジェクトを再開できます。


19

まず、このIMOを理解するための最良の情報源は、Roman Elizarovによる講演「Deep Dive into Coroutines」です。

コルーチンまたは関数が中断されますか?

中断の呼び出しINGの機能が一時停止sで、現在のスレッドが別のコルーチンの実行を開始することができることを意味し、コルーチンを。したがって、コルーチンは機能ではなく一時停止されていると言われています。

実際、サスペンド機能の呼び出しサイトは、このため「サスペンドポイント」と呼ばれています。

どのコルーチンが一時停止されますか?

コードを見て、何が起こっているかを分析してみましょう。

// 1. this call starts a new coroutine (let's call it C1).
//    If there were code after it, it would be executed concurrently with
//    the body of this async
async {
    ...
    // 2. this is a regular function call
    val deferred = computation()
    // 4. because await() is suspendING, it suspends coroutine C1.
    //    This means that if we had a single thread in our dispatcher, 
    //    it would now be free to go execute C2
    // 7. once C2 completes, C1 is resumed with the result `true` of C2's async
    val result = deferred.await() 
    ...
    // 8. C1 can now keep going in the current thread until it gets 
    //    suspended again (or not)
}

fun computation(): Deferred<Boolean> {
    // 3. this async call starts a second coroutine (C2). Depending on the 
    //    dispatcher you're using, you may have one or more threads.
    // 3.a. If you have multiple threads, the block of this async could be
    //      executed in parallel of C1 in another thread. The control flow 
    //      of the current thread returns to the caller of computation().
    // 3.b. If you have only one thread, the block is sort of "queued" but 
    //      not executed right away, and the control flow returns to the 
    //      caller of computation(). (unless a special dispatcher or 
    //      coroutine start argument is used, but let's keep it simple).
    //    In both cases, we say that this block executes "concurrently"
    //    with C1.
    return async {
        // 5. this may now be executed
        true
        // 6. C2 is now completed, so the thread can go back to executing 
        //    another coroutine (e.g. C1 here)
    }
}

アウターasyncはコルーチンを開始します。を呼び出すcomputation()と、インナーasyncは2番目のコルーチンを開始します。その後、への呼び出しawait()の中断実行外側 asyncの実行までのコルーチン、内部 asyncのコルーチンは終わりました。

単一のスレッドでもそれを見ることができます:スレッドは外部asyncの最初を実行し、次に呼び出しcomputation()て内部に到達しasyncます。この時点で、内部非同期の本体はスキップされ、スレッドはasyncに達するまで外部の実行を続けawait()ます。 await()await一時停止機能なので、「一時停止ポイント」です。これは、外側のコルーチンが中断され、スレッドが内側のコルーチンの実行を開始することを意味します。完了すると、アウターの終わりを実行するために戻りasyncます。

一時停止は、外部非同期コルーチンが内部計算コルーチンの終了を待っている(待機している)間、アイドル状態(つまり、サスペンドという名前)でアイドル状態になり、スレッドプールにスレッドを返し、子計算コルーチンが終了したときを意味しますか? 、それ(外部非同期コルーチン)が起動し、プールから別のスレッドを取得して続行しますか?

はい、正確に。

これが実際に達成される方法は、すべての中断機能をステートマシンに変換することです。各「状態」はこの中断機能内の中断ポイントに対応します。内部的には、関数を複数回呼び出すことができ、実行を開始する一時停止ポイントに関する情報が含まれています(詳細については、リンクしたビデオを実際にご覧ください)。


3
正解です。コルーチンに関しては、そのような本当に基本的な説明はありません。
bernardo.g

それが他の言語で実装されていないのはなぜですか?それとも何か不足していますか?私はそのソリューションについて長い間考えています
。Kotlin

@PEZOのコルーチンは長い間存在しています。Kotlinはそれらを発明しませんでしたが、構文とライブラリはそれらを輝かせます。Goにはゴルーチンがあり、JavaScriptとTypeScriptには約束があります。唯一の違いは、それらを使用する構文の詳細です。JSのasync機能がこのようにマークされているにもかかわらず、Promiseを返すのは非常に煩わしい/邪魔だと思います。
ジョフリー

申し訳ありませんが、私のコメントは明確ではありませんでした。私はsuspendキーワードを参照しています。これは非同期と同じではありません。
PEZO

Romanのビデオを紹介していただきありがとうございます。純金。
Denounce'IN

8

キーワードとプロパティをsuspend類比することが理解するための最良の方法であることがわかりました。thiscoroutineContext

Kotlin関数は、ローカルまたはグローバルとして宣言できます。ローカル関数は魔法のようにthisキーワードにアクセスできますが、グローバル関数はアクセスできません。

Kotlin関数は、suspendまたはブロッキングとして宣言できます。suspend関数は魔法のようにcoroutineContextプロパティにアクセスできますが、ブロック関数はそうではありません。

事はある:coroutineContextプロパティは、 「通常の」プロパティのように宣言されて Kotlin STDLIBでなく、この宣言は、ドキュメント/ナビゲーションの目的のためだけのスタブです。実際coroutineContext組み込みの組み込みプロパティです。つまり、内部でコンパイラは、言語のキーワードを認識するように、このプロパティをマジックで認識しています。

どのようなthisキーワード地元の機能のためにすることは何であるcoroutineContextプロパティがの場合とsuspend機能:それは実行の現在のコンテキストへのアクセスを提供します。

したがって、プロパティsuspendへのアクセスを取得する必要がありcoroutineContextます-現在実行されているコルーチンコンテキストのインスタンス


5

継続の概念の簡単な例を挙げたいと思います。これはサスペンド機能が行うことであり、フリーズ/サスペンドしてから続行/再開できます。スレッドとセマフォの観点からコルーチンを考えるのをやめます。継続、さらにはコールバックフックの観点から考えてみてください。

明確にするために、コルーチンはsuspend関数を使用して一時停止できます。これを調査しましょう:

アンドロイドでは、例えばこれを行うことができます:

var TAG = "myTAG:"
        fun myMethod() { // function A in image
            viewModelScope.launch(Dispatchers.Default) {
                for (i in 10..15) {
                    if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended`)
                        println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
                        freezePleaseIAmDoingHeavyWork()
                    } else
                        println("$TAG $i")
                    }
            }

            //this area is not suspended, you can continue doing work
        }


        suspend fun freezePleaseIAmDoingHeavyWork() { // function B in image
            withContext(Dispatchers.Default) {
                async {
                    //pretend this is a big network call
                    for (i in 1..10) {
                        println("$TAG $i")
                        delay(1_000)//delay pauses coroutine, NOT the thread. use  Thread.sleep if you want to pause a thread. 
                    }
                    println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
                }
            }
        }

上記のコードは以下を出力します:

I: myTAG: my coroutine is frozen but i can carry on to do other things

I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done

I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10

I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume

I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15

次のように機能することを想像してみてください。

ここに画像の説明を入力してください

そのため、起動した現在の関数は停止せず、コルーチンが継続している間だけ中断します。スレッドは、suspend関数を実行しても一時停止されません。

私はこのサイトがあなたが物事をまっすぐにするのを助けることができると思いますそして私の参照です。

クールなことをして、イテレーションの途中で中断機能をフリーズしましょう。後で再開しますonResume

呼び出された変数をcontinuation保存し、それをコルーチン継続オブジェクトとともにロードします。

var continuation: CancellableContinuation<String>? = null

suspend fun freezeHere() = suspendCancellableCoroutine<String> {
            continuation = it
        }

 fun unFreeze() {
            continuation?.resume("im resuming") {}
        }

次に、中断された関数に戻り、反復の途中でフリーズさせます。

 suspend fun freezePleaseIAmDoingHeavyWork() {
        withContext(Dispatchers.Default) {
            async {
                //pretend this is a big network call
                for (i in 1..10) {
                    println("$TAG $i")
                    delay(1_000)
                    if(i == 3)
                        freezeHere() //dead pause, do not go any further
                }
            }
        }
    }

次に、onResumeのような他の場所(たとえば):

override fun onResume() {
        super.onResume()
        unFreeze()
    }

そしてループは続きます。一時停止機能をいつでもフリーズして、しばらくしてから再開できることを知っているのは、とてもきちんとしています。チャンネルを調べることもできます


4

すでに多くの良い答えがあるので、他の人のためにもっと簡単な例を投稿したいと思います。

runBlockingの使用例:

  • myMethod()は suspend関数です
  • runBlocking { }ブロッキング方法でコルーチンを開始します。これは、Threadクラスで通常のスレッドをブロックし、特定のイベントの後にブロックされたスレッドに通知する方法に似ています。
  • runBlocking { }ないブロックコルーチンまでスレッドを実行する電流を、(ボディ間{})が完了されます

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
        runBlocking {
            Log.d(TAG,"Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
            myMethod();
        }
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
    }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
        for(i in 1..5) {
            Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
        }
    }

この出力:

I/TAG: Outer code started on Thread : main
D/TAG: Inner code started  on Thread : main making outer code suspend
// ---- main thread blocked here, it will wait until coroutine gets completed ----
D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- main thread resumes as coroutine is completed ----
I/TAG: Outer code resumed on Thread : main

ユースケースを起動する:

  • launch { } 同時にコルーチンを開始します。
  • これは、起動を指定すると、コルーチンが実行を開始することを意味します workerスレッドでます。
  • workerスレッドと外側のスレッドが(そこから私たちはと呼ばれるlaunch { })の両方が同時に実行されます。内部的には、JVMはプリエンプティブスレッドを実行する場合があります
  • 複数のタスクを並行して実行する必要がある場合は、これを使用できます。scopesコルーチンの寿命を指定するものがあります。を指定したGlobalScope場合、コルーチンはアプリケーションのライフタイムが終了するまで機能します。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
    
        GlobalScope.launch(Dispatchers.Default) {
            Log.d(TAG,"Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
            myMethod();
        }
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
    }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
            for(i in 1..5) {
                Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }

この出力:

10806-10806/com.example.viewmodelapp I/TAG: Outer code started on Thread : main
10806-10806/com.example.viewmodelapp I/TAG: Outer code resumed on Thread : main
// ---- In this example, main had only 2 lines to execute. So, worker thread logs start only after main thread logs complete
// ---- In some cases, where main has more work to do, the worker thread logs get overlap with main thread logs
10806-10858/com.example.viewmodelapp D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-1 making outer code suspend
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-1

非同期待機するユースケース:

  • 複数のタスクを実行する必要があり、それらが他のタスクの完了に依存している場合、asyncそしてawait役立つだろう。
  • たとえば、以下のコードには、2中断関数myMethod()およびmyMethod2()があります。myMethod2()完全な完了後にのみ実行されるはずですmyMethod() または myMethod2()の結果に依存しmyMethod()、私たちが使用することができますasyncし、await
  • async 同様にコルーチンを並列で開始します launchます。しかし、これは、あるコルーチンを待ってから別のコルーチンを並行して開始する方法を提供します。
  • その方法ですawait()asyncのインスタンスを返しますDeffered<T>TなりUnit、デフォルトのために。私たちはどんなのを待つ必要がある場合asyncの完了、我々は呼び出す必要が.await()Deffered<T>ているのインスタンスasync。以下の例のように、呼び出しが完了innerAsync.await()すると、実行innerAsyncが完了するまで中断されます。出力でも同じことが観察できます。innerAsync呼び出す、最初に完成しますmyMethod()。次に、次のasync innerAsync2呼び出しが始まりますmyMethod2()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
    
         job = GlobalScope.launch(Dispatchers.Default) {
             innerAsync = async {
                 Log.d(TAG, "Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
                 myMethod();
             }
             innerAsync.await()
    
             innerAsync2 = async {
                 Log.w(TAG, "Inner code started  on Thread : " + Thread.currentThread().name + " making outer code suspend");
                 myMethod2();
             }
        }
    
        Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
        }
    
    private suspend fun myMethod() {
        withContext(Dispatchers.Default) {
            for(i in 1..5) {
                Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }
    
    private suspend fun myMethod2() {
        withContext(Dispatchers.Default) {
            for(i in 1..10) {
                Log.w(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
            }
        }
    }

この出力:

11814-11814/? I/TAG: Outer code started on Thread : main
11814-11814/? I/TAG: Outer code resumed on Thread : main
11814-11845/? D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-2 making outer code suspend
11814-11845/? D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- Due to await() call, innerAsync2 will start only after innerAsync gets completed
11814-11848/? W/TAG: Inner code started  on Thread : DefaultDispatcher-worker-4 making outer code suspend
11814-11848/? W/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 6 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 7 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 8 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 9 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 10 on Thread : DefaultDispatcher-worker-4
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.