@@ -45,6 +45,7 @@ import com.ai.assistance.operit.R
4545import java.util.concurrent.ConcurrentHashMap
4646import java.util.concurrent.atomic.AtomicBoolean
4747import java.util.concurrent.atomic.AtomicInteger
48+ import kotlinx.coroutines.CancellationException
4849import kotlinx.coroutines.CoroutineScope
4950import kotlinx.coroutines.Dispatchers
5051import kotlinx.coroutines.Job
@@ -346,13 +347,42 @@ class EnhancedAIService private constructor(private val context: Context) {
346347
347348 // Execution context for a single sendMessage call to achieve concurrency
348349 private data class MessageExecutionContext (
350+ val executionId : Int ,
349351 val streamBuffer : StringBuilder = StringBuilder (),
350352 val roundManager : ConversationRoundManager = ConversationRoundManager (),
351353 val isConversationActive : AtomicBoolean = AtomicBoolean (true),
352354 val conversationHistory : MutableList <Pair <String , String >>,
353355 val eventChannel : MutableSharedStream <TextStreamEvent >,
354356 )
355357
358+ private val activeExecutionContexts = ConcurrentHashMap <Int , MessageExecutionContext >()
359+ private val nextExecutionContextId = AtomicInteger (0 )
360+
361+ private fun registerExecutionContext (context : MessageExecutionContext ) {
362+ activeExecutionContexts[context.executionId] = context
363+ }
364+
365+ private fun unregisterExecutionContext (context : MessageExecutionContext ) {
366+ activeExecutionContexts.remove(context.executionId, context)
367+ }
368+
369+ private fun invalidateExecutionContext (context : MessageExecutionContext , reason : String ) {
370+ if (context.isConversationActive.compareAndSet(true , false )) {
371+ AppLogger .d(TAG , " 执行上下文已失效: id=${context.executionId} , reason=$reason " )
372+ }
373+ }
374+
375+ private fun invalidateAllExecutionContexts (reason : String ) {
376+ activeExecutionContexts.values.forEach { context ->
377+ invalidateExecutionContext(context, reason)
378+ }
379+ }
380+
381+ private fun isExecutionContextActive (context : MessageExecutionContext ): Boolean {
382+ return context.isConversationActive.get() &&
383+ activeExecutionContexts[context.executionId] == = context
384+ }
385+
356386 private suspend fun startAssistantResponseRound (context : MessageExecutionContext ) {
357387 context.roundManager.startNewRound()
358388 context.streamBuffer.clear()
@@ -551,9 +581,11 @@ class EnhancedAIService private constructor(private val context: Context) {
551581 val wrappedStream = stream {
552582 val execContext =
553583 MessageExecutionContext (
584+ executionId = nextExecutionContextId.incrementAndGet(),
554585 conversationHistory = chatHistory.toMutableList(),
555586 eventChannel = eventChannel
556587 )
588+ registerExecutionContext(execContext)
557589 var hadFatalError = false
558590 try {
559591 // 确保所有操作都在IO线程上执行
@@ -808,16 +840,18 @@ class EnhancedAIService private constructor(private val context: Context) {
808840 " 流收集完成,总计 $totalChars 字符,耗时: ${System .currentTimeMillis() - streamStartTime} ms"
809841 )
810842 }
843+ } catch (e: CancellationException ) {
844+ invalidateExecutionContext(execContext, " sendMessage.collect.cancelled" )
845+ AppLogger .d(TAG , " sendMessage流被取消" )
846+ throw e
811847 } catch (e: Exception ) {
812- // 对于协程取消异常,这是正常流程,应当向上抛出以停止流
813- if (e is kotlinx.coroutines.CancellationException ) {
814- AppLogger .d(TAG , " sendMessage流被取消" )
815- throw e
816- }
817-
818848 // 用户取消导致的 Socket closed 是预期行为,不应作为错误处理
819849 if (e.message?.contains(" Socket closed" , ignoreCase = true ) == true ) {
820- AppLogger .d(TAG , " Stream was cancelled by the user (Socket closed)." )
850+ if (isExecutionContextActive(execContext)) {
851+ AppLogger .d(TAG , " Stream was cancelled by the user (Socket closed)." )
852+ } else {
853+ AppLogger .d(TAG , " Stream closed after execution context was invalidated." )
854+ }
821855 } else {
822856 hadFatalError = true
823857 // Handle any exceptions
@@ -833,32 +867,41 @@ class EnhancedAIService private constructor(private val context: Context) {
833867 if (! isSubTask) stopAiService()
834868 }
835869 } finally {
836- // 确保流处理完成后调用
837- if (! hadFatalError) {
838- val collector = this
839- withContext(Dispatchers .IO ) {
840- processStreamCompletion(
841- execContext,
842- functionType,
843- collector,
844- enableThinking,
845- enableMemoryQuery,
846- onNonFatalError,
847- onTokenLimitExceeded,
848- maxTokens,
849- tokenUsageThreshold,
850- isSubTask,
851- characterName,
852- avatarUri,
853- roleCardId,
854- chatId,
855- onToolInvocation,
856- chatModelConfigIdOverride,
857- chatModelIndexOverride,
858- stream,
859- enableGroupOrchestrationHint
870+ try {
871+ // 确保流处理完成后调用;如果本轮已被取消,则不能再继续跑完成逻辑。
872+ if (! hadFatalError && isExecutionContextActive(execContext)) {
873+ val collector = this
874+ withContext(Dispatchers .IO ) {
875+ processStreamCompletion(
876+ execContext,
877+ functionType,
878+ collector,
879+ enableThinking,
880+ enableMemoryQuery,
881+ onNonFatalError,
882+ onTokenLimitExceeded,
883+ maxTokens,
884+ tokenUsageThreshold,
885+ isSubTask,
886+ characterName,
887+ avatarUri,
888+ roleCardId,
889+ chatId,
890+ onToolInvocation,
891+ chatModelConfigIdOverride,
892+ chatModelIndexOverride,
893+ stream,
894+ enableGroupOrchestrationHint
895+ )
896+ }
897+ } else if (! hadFatalError) {
898+ AppLogger .d(
899+ TAG ,
900+ " 跳过流完成处理:执行上下文已失效, id=${execContext.executionId} "
860901 )
861902 }
903+ } finally {
904+ unregisterExecutionContext(execContext)
862905 }
863906 }
864907 }
@@ -1956,6 +1999,9 @@ class EnhancedAIService private constructor(private val context: Context) {
19561999 stream,
19572000 enableGroupOrchestrationHint
19582001 )
2002+ } catch (e: CancellationException ) {
2003+ AppLogger .d(TAG , " 处理工具执行结果被取消" )
2004+ throw e
19592005 } catch (e: Exception ) {
19602006 AppLogger .e(TAG , " 处理工具执行结果时出错" , e)
19612007 withContext(Dispatchers .Main ) {
@@ -2156,6 +2202,8 @@ class EnhancedAIService private constructor(private val context: Context) {
21562202
21572203 /* * Cancel the current conversation */
21582204 fun cancelConversation () {
2205+ invalidateAllExecutionContexts(" cancelConversation" )
2206+
21592207 // Set conversation inactive
21602208 // isConversationActive.set(false) // This is now per-context, can't set a global one
21612209
0 commit comments