mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-20 21:42:24 +08:00
Improve Bot life cycle management, close #317
This commit is contained in:
parent
aa2805b81f
commit
06205cb69b
@ -404,27 +404,15 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
|
|||||||
logger.info { "Syncing friend message history: Success" }
|
logger.info { "Syncing friend message history: Success" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doHeartBeat(): Exception? {
|
private suspend fun doHeartBeat(): Throwable? {
|
||||||
val lastException: Exception?
|
return retryCatching(2) {
|
||||||
try {
|
|
||||||
kotlin.runCatching {
|
|
||||||
Heartbeat.Alive(bot.client)
|
Heartbeat.Alive(bot.client)
|
||||||
.sendAndExpect<Heartbeat.Alive.Response>(
|
.sendAndExpect<Heartbeat.Alive.Response>(
|
||||||
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
|
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
|
||||||
retry = 2
|
retry = 2
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}.exceptionOrNull()
|
||||||
Heartbeat.Alive(bot.client)
|
|
||||||
.sendAndExpect<Heartbeat.Alive.Response>(
|
|
||||||
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
|
|
||||||
retry = 2
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
lastException = e
|
|
||||||
}
|
|
||||||
return lastException
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +24,7 @@ internal object MessageSvcPushForceOffline :
|
|||||||
OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvc.PushForceOffline") {
|
OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvc.PushForceOffline") {
|
||||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
|
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
|
||||||
val struct = this.readUniPacket(RequestPushForceOffline.serializer())
|
val struct = this.readUniPacket(RequestPushForceOffline.serializer())
|
||||||
|
@Suppress("INVISIBLE_MEMBER")
|
||||||
return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
|
return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,6 +189,7 @@ internal class StatSvc {
|
|||||||
|
|
||||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): BotOfflineEvent.Dropped {
|
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): BotOfflineEvent.Dropped {
|
||||||
val decodeUniPacket = readUniPacket(RequestMSFForceOffline.serializer())
|
val decodeUniPacket = readUniPacket(RequestMSFForceOffline.serializer())
|
||||||
|
@Suppress("INVISIBLE_MEMBER")
|
||||||
return BotOfflineEvent.Dropped(bot, MsfOfflineToken(decodeUniPacket.uin, decodeUniPacket.iSeqno, 0))
|
return BotOfflineEvent.Dropped(bot, MsfOfflineToken(decodeUniPacket.uin, decodeUniPacket.iSeqno, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,15 +7,14 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE", "UnusedImport",
|
@file:Suppress(
|
||||||
"EXPERIMENTAL_OVERRIDE")
|
"EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE", "UnusedImport",
|
||||||
|
"EXPERIMENTAL_OVERRIDE"
|
||||||
|
)
|
||||||
|
|
||||||
package net.mamoe.mirai
|
package net.mamoe.mirai
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
|
import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
|
||||||
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
|
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
|
||||||
@ -35,13 +34,12 @@ import kotlin.jvm.JvmSynthetic
|
|||||||
*/
|
*/
|
||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
|
suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
|
||||||
// 任何人都能看到这个方法
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 机器人对象. 一个机器人实例登录一个 QQ 账号.
|
* 机器人对象. 一个机器人实例登录一个 QQ 账号.
|
||||||
* Mirai 为多账号设计, 可同时维护多个机器人.
|
* Mirai 为多账号设计, 可同时维护多个机器人.
|
||||||
*
|
*
|
||||||
* 注: Bot 为全协程实现, 没有其他任务时若不使用 [join], 主线程将会退出.
|
* 有关 [Bot] 生命管理, 请查看 [BotConfiguration.inheritCoroutineContext]
|
||||||
*
|
*
|
||||||
* @see Contact 联系人
|
* @see Contact 联系人
|
||||||
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
|
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
|
||||||
@ -278,6 +276,13 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
|||||||
abstract val network: BotNetworkHandler
|
abstract val network: BotNetworkHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 [Job] 的协程 [Job]. 此 [Job] 为一个 [SupervisorJob]
|
||||||
|
*/
|
||||||
|
@get:JvmSynthetic
|
||||||
|
inline val Bot.supervisorJob: CompletableJob
|
||||||
|
get() = this.coroutineContext[Job] as CompletableJob
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 挂起协程直到 [Bot] 下线.
|
* 挂起协程直到 [Bot] 下线.
|
||||||
*/
|
*/
|
||||||
@ -305,13 +310,13 @@ suspend inline fun Bot.recall(message: MessageChain) =
|
|||||||
* @see recall
|
* @see recall
|
||||||
*/
|
*/
|
||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
inline fun Bot.recallIn(
|
inline fun CoroutineScope.recallIn(
|
||||||
source: MessageSource,
|
source: MessageSource,
|
||||||
millis: Long,
|
millis: Long,
|
||||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||||
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
||||||
kotlinx.coroutines.delay(millis)
|
delay(millis)
|
||||||
recall(source)
|
source.recall()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -322,13 +327,13 @@ inline fun Bot.recallIn(
|
|||||||
* @see recall
|
* @see recall
|
||||||
*/
|
*/
|
||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
inline fun Bot.recallIn(
|
inline fun CoroutineScope.recallIn(
|
||||||
message: MessageChain,
|
message: MessageChain,
|
||||||
millis: Long,
|
millis: Long,
|
||||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||||
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
|
||||||
kotlinx.coroutines.delay(millis)
|
delay(millis)
|
||||||
recall(message)
|
message.recall()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,11 +91,15 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
private val offlineListener: Listener<BotOfflineEvent> =
|
private val offlineListener: Listener<BotOfflineEvent> =
|
||||||
this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
|
this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
|
||||||
if (event.bot != this.bot) {
|
if (event.bot != this@BotImpl) {
|
||||||
|
return@subscribeAlways
|
||||||
|
}
|
||||||
|
if (!::_network.isInitialized) {
|
||||||
|
// bot 还未登录就被 close
|
||||||
return@subscribeAlways
|
return@subscribeAlways
|
||||||
}
|
}
|
||||||
if (network.areYouOk() && event !is BotOfflineEvent.Force) {
|
if (network.areYouOk() && event !is BotOfflineEvent.Force) {
|
||||||
// avoid concurrent re-login tasks
|
// network 运行正常
|
||||||
return@subscribeAlways
|
return@subscribeAlways
|
||||||
}
|
}
|
||||||
when (event) {
|
when (event) {
|
||||||
@ -262,14 +266,14 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
|||||||
this.launch {
|
this.launch {
|
||||||
BotOfflineEvent.Active(this@BotImpl, cause).broadcast()
|
BotOfflineEvent.Active(this@BotImpl, cause).broadcast()
|
||||||
}
|
}
|
||||||
|
logger.info { "Bot cancelled" + cause?.message?.let { ": $it" }.orEmpty() }
|
||||||
if (cause == null) {
|
if (cause == null) {
|
||||||
this.cancel()
|
supervisorJob.cancel()
|
||||||
} else {
|
} else {
|
||||||
this.cancel(CancellationException("bot cancelled", cause))
|
supervisorJob.cancel(CancellationException("Bot closed", cause))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||||
internal annotation class ThisApiMustBeUsedInWithConnectionLockBlock
|
internal annotation class ThisApiMustBeUsedInWithConnectionLockBlock
|
@ -51,7 +51,7 @@ class EventCancelledException : RuntimeException {
|
|||||||
/**
|
/**
|
||||||
* [Bot] 登录完成, 好友列表, 群组列表初始化完成
|
* [Bot] 登录完成, 好友列表, 群组列表初始化完成
|
||||||
*/
|
*/
|
||||||
data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent, AbstractEvent()
|
data class BotOnlineEvent internal constructor(override val bot: Bot) : BotActiveEvent, AbstractEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Bot] 离线.
|
* [Bot] 离线.
|
||||||
@ -59,31 +59,33 @@ data class BotOnlineEvent(override val bot: Bot) : BotActiveEvent, AbstractEvent
|
|||||||
sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
|
sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主动离线
|
* 主动离线. 主动广播这个事件也可以让 [Bot] 关闭.
|
||||||
*/
|
*/
|
||||||
data class Active(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), BotActiveEvent
|
data class Active(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), BotActiveEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 被挤下线
|
* 被挤下线
|
||||||
*/
|
*/
|
||||||
data class Force(override val bot: Bot, val title: String, val message: String) : BotOfflineEvent(), Packet,
|
data class Force internal constructor(override val bot: Bot, val title: String, val message: String) :
|
||||||
|
BotOfflineEvent(), Packet,
|
||||||
BotPassiveEvent
|
BotPassiveEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 被服务器断开或因网络问题而掉线
|
* 被服务器断开或因网络问题而掉线
|
||||||
*/
|
*/
|
||||||
data class Dropped(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), Packet, BotPassiveEvent
|
data class Dropped internal constructor(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), Packet,
|
||||||
|
BotPassiveEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器主动要求更换另一个服务器
|
* 服务器主动要求更换另一个服务器
|
||||||
*/
|
*/
|
||||||
data class RequireReconnect(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
|
data class RequireReconnect internal constructor(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Bot] 主动或被动重新登录.
|
* [Bot] 主动或被动重新登录.
|
||||||
*/
|
*/
|
||||||
data class BotReloginEvent(
|
data class BotReloginEvent internal constructor(
|
||||||
override val bot: Bot,
|
override val bot: Bot,
|
||||||
val cause: Throwable?
|
val cause: Throwable?
|
||||||
) : BotEvent, BotActiveEvent, AbstractEvent()
|
) : BotEvent, BotActiveEvent, AbstractEvent()
|
||||||
|
@ -13,15 +13,18 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 表明这个 API 是为了让 Java 使用者调用更方便.
|
* 表明这个 API 是为了让 Java 使用者调用更方便.
|
||||||
|
*
|
||||||
* 一般有一定的性能损失, 且不能在 JVM/Android 以外平台使用. 不要在 Kotlin 调用它.
|
* 一般有一定的性能损失, 且不能在 JVM/Android 以外平台使用. 不要在 Kotlin 调用它.
|
||||||
*/
|
*/
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.CLASS)
|
||||||
annotation class JavaFriendlyAPI
|
internal annotation class JavaFriendlyAPI
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Bot] 中为了让 Java 使用者调用更方便的 API 列表.
|
* [Bot] 中为了让 Java 使用者调用更方便的 API 列表.
|
||||||
|
*
|
||||||
|
* **注意**: 不应该把这个类作为一个类型, 只应使用其中的方法
|
||||||
*/
|
*/
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "unused")
|
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "unused")
|
||||||
|
@ -30,6 +30,10 @@ annotation class LowLevelAPI
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* [Bot] 相关协议层低级 API.
|
* [Bot] 相关协议层低级 API.
|
||||||
|
*
|
||||||
|
* **注意**: 不应该把这个类作为一个类型, 只应使用其中的方法
|
||||||
|
*
|
||||||
|
* **警告**: 所有的低级 API 都可能在任意时刻不经过任何警告和迭代就被修改. 因此非常不建议在任何情况下使用这些 API.
|
||||||
*/
|
*/
|
||||||
@MiraiExperimentalAPI
|
@MiraiExperimentalAPI
|
||||||
@Suppress("FunctionName", "unused")
|
@Suppress("FunctionName", "unused")
|
||||||
|
@ -331,19 +331,15 @@ inline fun MessageSource.isAboutFriend(): Boolean {
|
|||||||
* 引用这条消息
|
* 引用这条消息
|
||||||
* @see QuoteReply
|
* @see QuoteReply
|
||||||
*/
|
*/
|
||||||
fun MessageSource.quote(): QuoteReply {
|
@JvmSynthetic
|
||||||
|
inline fun MessageSource.quote(): QuoteReply = QuoteReply(this)
|
||||||
return QuoteReply(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 引用这条消息. 仅从服务器接收的消息 (即来自 [MessageEvent]) 才可以通过这个方式被引用.
|
* 引用这条消息. 仅从服务器接收的消息 (即来自 [MessageEvent]) 才可以通过这个方式被引用.
|
||||||
* @see QuoteReply
|
* @see QuoteReply
|
||||||
*/
|
*/
|
||||||
fun MessageChain.quote(): QuoteReply {
|
@JvmSynthetic
|
||||||
|
inline fun MessageChain.quote(): QuoteReply = QuoteReply(this.source)
|
||||||
return QuoteReply(this.source)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
|
* 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
|
||||||
|
@ -27,7 +27,7 @@ sealed class LoginFailedException constructor(
|
|||||||
) : RuntimeException(message, cause)
|
) : RuntimeException(message, cause)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码输入错误
|
* 密码输入错误 (有时候也会是其他错误, 如 `"当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。"`)
|
||||||
*/
|
*/
|
||||||
class WrongPasswordException(message: String?) : LoginFailedException(true, message)
|
class WrongPasswordException(message: String?) : LoginFailedException(true, message)
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.network.BotNetworkHandler
|
import net.mamoe.mirai.network.BotNetworkHandler
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@ -34,7 +35,7 @@ open class BotConfiguration {
|
|||||||
/** 设备信息覆盖. 默认使用随机的设备信息. */
|
/** 设备信息覆盖. 默认使用随机的设备信息. */
|
||||||
var deviceInfo: ((Context) -> DeviceInfo)? = null
|
var deviceInfo: ((Context) -> DeviceInfo)? = null
|
||||||
|
|
||||||
/** 父 [CoroutineContext]. [Bot] 创建后会覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
|
/** 父 [CoroutineContext]. [Bot] 创建后会使用 [SupervisorJob] 覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
|
||||||
var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||||
|
|
||||||
/** 心跳周期. 过长会导致被服务器断开连接. */
|
/** 心跳周期. 过长会导致被服务器断开连接. */
|
||||||
@ -115,21 +116,64 @@ open class BotConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext]
|
* 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
|
||||||
|
*
|
||||||
|
* Bot 将会使用一个 [SupervisorJob] 覆盖 [coroutineContext] 当前协程的 [Job], 并使用当前协程的 [Job] 作为父 [Job]
|
||||||
*
|
*
|
||||||
* 用例:
|
* 用例:
|
||||||
* ```
|
* ```
|
||||||
* coroutineScope {
|
* coroutineScope {
|
||||||
* val bot = Bot(...)
|
* val bot = Bot(...) {
|
||||||
|
* inheritCoroutineContext()
|
||||||
|
* }
|
||||||
* bot.login()
|
* bot.login()
|
||||||
* } // coroutineScope 会等待 Bot 退出
|
* } // coroutineScope 会等待 Bot 退出
|
||||||
* ```
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* **注意**: `bot.cancel` 时将会让父 [Job] 也被 cancel.
|
||||||
|
* ```
|
||||||
|
* coroutineScope { // this: CoroutineScope
|
||||||
|
* launch {
|
||||||
|
* while(isActive) {
|
||||||
|
* delay(500)
|
||||||
|
* println("I'm alive")
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* val bot = Bot(...) {
|
||||||
|
* inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
|
||||||
|
* }
|
||||||
|
* bot.login()
|
||||||
|
* bot.cancel() // 取消了整个 `coroutineScope`, 因此上文不断打印 `"I'm alive"` 的协程也会被取消.
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 因此, 此函数尤为适合在 `suspend fun main()` 中使用, 它能阻止主线程退出:
|
||||||
|
* ```
|
||||||
|
* suspend fun main() {
|
||||||
|
* val bot = Bot() {
|
||||||
|
* inheritCoroutineContext()
|
||||||
|
* }
|
||||||
|
* bot.subscribe { ... }
|
||||||
|
*
|
||||||
|
* // 主线程不会退出, 直到 Bot 离线.
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 简言之,
|
||||||
|
* - 若想让 [Bot] 作为 '守护进程' 运行, 则无需调用 [inheritCoroutineContext].
|
||||||
|
* - 若想让 [Bot] 依赖于当前协程, 让当前协程等待 [Bot] 运行, 则使用 [inheritCoroutineContext]
|
||||||
|
*
|
||||||
|
* @see parentCoroutineContext
|
||||||
*/
|
*/
|
||||||
@ConfigurationDsl
|
@ConfigurationDsl
|
||||||
suspend inline fun inheritCoroutineContext() {
|
suspend inline fun inheritCoroutineContext() {
|
||||||
parentCoroutineContext = coroutineContext
|
parentCoroutineContext = coroutineContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 标注一个配置 DSL 函数 */
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
@DslMarker
|
@DslMarker
|
||||||
annotation class ConfigurationDsl
|
annotation class ConfigurationDsl
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user