简体   繁体   English

Kotlin 用协程同步线程

[英]Kotlin Synchronize Thread with Coroutine

Currently I have a main thread that constantly loops:目前我有一个不断循环的主线程:

var suspension = Suspension()

fun loop() {
    // Doing stuff here...
        
    suspension.callTick()
        
    // Doing more stuff here...
}

It calls the callTick() method which sends data to a channel:它调用将数据发送到通道的 callTick() 方法:

class Suspension {
    private val ticksChannel = Channel<Unit>(0)

    fun callTick() {
        ticksChannel.trySend(Unit)
    }

    suspend fun waitTick() {
        ticksChannel.receive()
    }
}

Now my last class makes use of this:现在我的最后一个 class 使用了这个:

class Task(private val suspension: Suspension) {
    suspend fun runTask() {
        while (true) {
            suspension.waitTick()

            someMethodOnTheMainThread()
        }
    }
}

Now I'm wondering how I can call the someMethodOnTheMainThread() method from the main thread.现在我想知道如何从主线程调用 someMethodOnTheMainThread() 方法。 The function has to be called right after the 'suspension.callTick()' method from loop(). function 必须在 loop() 的“suspension.callTick()”方法之后立即调用。 At the moment I'm running the function from the coroutine thread.目前我正在从协程线程运行 function 。 This causes a lot of errors and null pointer exceptions because it's not synchronized with the main thread.这会导致很多错误和 null 指针异常,因为它与主线程不同步。

Basically I'm wondering how to block / lock the main thread until the suspension.waitTick() method is called and the code after it is ran.基本上我想知道如何阻塞/锁定主线程,直到调用suspend.waitTick()方法并运行它之后的代码。 Is this too complex?这太复杂了吗? Is there another way to make suspending functions work with synchronized code?是否有另一种方法可以使挂起函数与同步代码一起使用?

Ok, so I have tried to implement my own dispatcher for the Client thread as follows:好的,所以我尝试为客户端线程实现我自己的调度程序,如下所示:

/**
 * Dispatches execution onto Client event dispatching thread and provides native [delay] support.
 */
@Suppress("unused")
val Dispatchers.Client : ClientDispatcher
    get() = Client

/**
 * Dispatcher for Client event dispatching thread.
 *
 * This class provides type-safety and a point for future extensions.
 */
@OptIn(InternalCoroutinesApi::class)
sealed class ClientDispatcher : MainCoroutineDispatcher(), Delay {
    override fun dispatch(context: CoroutineContext, block: Runnable): Unit = ClientUtility.invokeLater(block)

    @OptIn(ExperimentalCoroutinesApi::class)
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        val timer = schedule(timeMillis, TimeUnit.MILLISECONDS, ActionListener {
            with(continuation) { resumeUndispatched(Unit) }
        })
        continuation.invokeOnCancellation { timer.stop() }
    }

    /** @suppress */
    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
        val timer = schedule(timeMillis, TimeUnit.MILLISECONDS, ActionListener {
            block.run()
        })
        return object : DisposableHandle {
            override fun dispose() {
                timer.stop()
            }
        }
    }

    private fun schedule(time: Long, unit: TimeUnit, action: ActionListener): Timer =
        Timer(unit.toMillis(time).coerceAtMost(Int.MAX_VALUE.toLong()).toInt(), action).apply {
            isRepeats = false
            start()
        }
}

@OptIn(InternalCoroutinesApi::class)
internal class ClientDispatcherFactory : MainDispatcherFactory {
    override val loadPriority: Int
        get() = 0

    override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher = Client
}

private object ImmediateClientDispatcher : ClientDispatcher() {
    override val immediate: MainCoroutineDispatcher
        get() = this

    override fun isDispatchNeeded(context: CoroutineContext): Boolean = !ClientUtility.isEventDispatchThread()

    @OptIn(InternalCoroutinesApi::class)
    override fun toString() = toStringInternalImpl() ?: "Client.immediate"
}

/**
 * Dispatches execution onto Client event dispatching thread and provides native [delay] support.
 */
internal object Client : ClientDispatcher() {
    override val immediate: MainCoroutineDispatcher
        get() = ImmediateClientDispatcher

    @OptIn(InternalCoroutinesApi::class)
    override fun toString() = toStringInternalImpl() ?: "Client"
}

It is practically the Swing dispatcher .它实际上是Swing 调度程序 Mainly I have changed the invokeLater method to my own implementation.主要是我将 invokeLater 方法更改为我自己的实现。 Another noteworthy modification is that I use my own isEventDispatchThread implementation.另一个值得注意的修改是我使用自己的 isEventDispatchThread 实现。

I am still using the Swing Timer class.我仍在使用 Swing 定时器 class。 I'm not sure if that's ok to do... But it seems to work from some testing.我不确定这是否可以......但它似乎可以通过一些测试来工作。

For the invokeLater and isEventDispatchThread implementation:对于 invokeLater 和 isEventDispatchThread 实现:

    val eventQueue = ConcurrentLinkedQueue<Runnable>()

    fun invokeLater(block: Runnable) {
        eventQueue.add(block)
    }

    fun isEventDispatchThread() = if (dispatchThread == null) {
            false
        } else {
            Thread.currentThread() == dispatchThread
        }

    var dispatchThread: Thread? = null

Then in the loop method:然后在循环方法中:

    fun loop() {
        // Doing stuff here

        suspension.callTick()

        ClientUtility.dispatchThread = Thread.currentThread()
        val iterator = ClientUtility.eventQueue.iterator()
        while (iterator.hasNext()) {
            val block = iterator.next()
            block.run()
            iterator.remove()
        }

        // Doing some more stuff here
    }

I now start the coroutine with: Dispatchers.Main.我现在启动协程:Dispatchers.Main。 Which dispatcher is the main I've defined in the resources/META-INF/services.kotlinx.coroutines.internal.MainDispatcherFactory file.哪个调度程序是我在resources/META-INF/services.kotlinx.coroutines.internal.MainDispatcherFactory文件中定义的主要调度程序。

To call the method someMethodOnTheMainThread() in the main thread and suspend current coroutine you can define it like the following:要在主线程中调用方法someMethodOnTheMainThread()并暂停当前协程,您可以将其定义如下:

suspend fun someMethodOnTheMainThread() = withContext(Dispatchers.Main) {
    // ... your code here
}

withContext - changes the context of a coroutine and runs code in the Main Thread. withContext - 更改协程的上下文并在主线程中运行代码。

Dispatchers.Main is available on Android if we use the kotlinx-coroutines-android artifact.如果我们使用kotlinx-coroutines-android工件,则Dispatchers.MainAndroid上可用。 Similarly, on JavaFX if we use kotlinx-coroutines-javafx , and on Swing if we use kotlinx-coroutines-swing .同样,在 JavaFX 上,如果我们使用kotlinx-coroutines-javafx ,在 Swing 上,如果我们使用kotlinx-coroutines-swing There are probably some other libraries that set it.可能还有其他一些设置它的库。 If you don't use any of them, this dispatcher is not configured, and it cannot be used, so you need to create your own dispatcher.如果你不使用其中任何一个,这个dispatcher没有配置,不能使用,所以你需要创建自己的dispatcher。

If you're not using a framework that takes control of the main thread (and would provide a main thread dispatcher), you can do something like this:如果您没有使用控制主线程的框架(并提供主线程调度程序),您可以执行以下操作:

var mainScope: CoroutineScope? = null;

fun main() {
    runBlocking {
        mainScope = this
        while(true) {
            //...
            suspension.callTick()
            // allow other coroutines to run
            yield()
            //...
        }
    }
}

And then if you want to run something on the main thread from elsewhere, you can just use mainScope?.launch .然后,如果您想从其他地方在主线程上运行某些东西,您可以使用mainScope?.launch

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM