perf(profile): optimize profile batch update and worker config

This commit is contained in:
Tunglies
2026-05-29 20:18:40 +08:00
parent 6e6c537e6b
commit 3444d594ac
3 changed files with 88 additions and 33 deletions
+20 -6
View File
@@ -103,6 +103,20 @@ export const ProfileItem = (props: Props) => {
const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(
undefined,
)
const setLoading = useCallback(
(loading: boolean) => {
setLoadingCache((cache) => {
const next = new Set(cache)
if (loading) {
next.add(itemData.uid)
} else {
next.delete(itemData.uid)
}
return next
})
},
[itemData.uid, setLoadingCache],
)
const { uid, name = 'Profile', extra, updated = 0, option } = itemData
@@ -251,7 +265,7 @@ export const ProfileItem = (props: Props) => {
100,
)
const loading = loadingCache[itemData.uid] ?? false
const loading = loadingCache.has(itemData.uid)
// interval update fromNow field
const [, forceRefresh] = useReducer((value: number) => value + 1, 0)
@@ -380,7 +394,7 @@ export const ProfileItem = (props: Props) => {
/// 2 至少使用一个代理,根据订阅,如果没订阅,默认使用系统代理
const onUpdate = useLockFn(async (type: 0 | 1 | 2): Promise<void> => {
setAnchorEl(null)
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: true }))
setLoading(true)
// 根据类型设置初始更新选项
const option: Partial<IProfileOption> = {}
@@ -408,7 +422,7 @@ export const ProfileItem = (props: Props) => {
// 更新完全失败(包括后端的回退尝试)
// 不需要做处理,后端会通过事件通知系统发送错误
} finally {
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false }))
setLoading(false)
}
})
@@ -599,7 +613,7 @@ export const ProfileItem = (props: Props) => {
Promise.allSettled([
listen<{ uid?: string }>('profile-update-started', ({ payload }) => {
if (payload.uid === itemData.uid) {
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: true }))
setLoading(true)
}
}),
listen<{ uid?: string }>('profile-update-completed', ({ payload }) => {
@@ -607,7 +621,7 @@ export const ProfileItem = (props: Props) => {
return
}
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false }))
setLoading(false)
// 刷新 profile 数据以获取最新的 updated 时间戳
void mutateProfiles()
// 更新完成后刷新显示
@@ -635,7 +649,7 @@ export const ProfileItem = (props: Props) => {
disposed = true
unlisteners.forEach((unlisten) => unlisten())
}
}, [fetchNextUpdateTime, itemData.uid, mutateProfiles, setLoadingCache])
}, [fetchNextUpdateTime, itemData.uid, mutateProfiles, setLoading])
const handleSaveProfileDocument = useLockFn(async () => {
const currentValue = profileDocument.value
+67 -26
View File
@@ -66,9 +66,16 @@ import {
} from '@/services/cmds'
import { showNotice } from '@/services/notice-service'
import { queryClient } from '@/services/query-client'
import { useSetLoadingCache, useThemeMode } from '@/services/states'
import {
useLoadingCache,
useSetLoadingCache,
useThemeMode,
} from '@/services/states'
import { debugLog } from '@/utils/debug'
// 与 src-tauri/src/main.rs 的 worker_limit 上限(8)保持一致,避免前后端更新风暴不对齐
const PROFILE_UPDATE_WORKER_LIMIT = 8
// 记录profile切换状态
const debugProfileSwitch = (action: string, profile: string, extra?: any) => {
const timestamp = new Date().toISOString().substring(11, 23)
@@ -602,34 +609,68 @@ const ProfilePage = () => {
})
// 更新所有订阅
const loadingCache = useLoadingCache()
const setLoadingCache = useSetLoadingCache()
const onUpdateAll = useLockFn(async () => {
const throttleMutate = throttle(mutateProfiles, 2000, {
trailing: true,
})
const updateOne = async (uid: string) => {
try {
await updateProfile(uid)
throttleMutate()
} catch (err: any) {
console.error(`更新订阅 ${uid} 失败:`, err)
} finally {
setLoadingCache((cache) => ({ ...cache, [uid]: false }))
}
}
return new Promise((resolve) => {
const setLoadingProfiles = useCallback(
(uids: string[], loading: boolean) => {
setLoadingCache((cache) => {
// 获取没有正在更新的订阅
const items = profileItems.filter(
(e) => e.type === 'remote' && !cache[e.uid],
)
const change = Object.fromEntries(items.map((e) => [e.uid, true]))
Promise.allSettled(items.map((e) => updateOne(e.uid))).then(resolve)
return { ...cache, ...change }
const next = new Set(cache)
for (const uid of uids) {
if (loading) {
next.add(uid)
} else {
next.delete(uid)
}
}
return next
})
})
},
[setLoadingCache],
)
const runProfileUpdates = useCallback(
async (uids: string[]) => {
if (uids.length === 0) return
const throttleMutate = throttle(mutateProfiles, 2000, {
trailing: true,
})
let cursor = 0
const updateOne = async (uid: string) => {
try {
await updateProfile(uid)
throttleMutate()
} catch (err: any) {
console.error(`更新订阅 ${uid} 失败:`, err)
}
}
const worker = async () => {
while (cursor < uids.length) {
const uid = uids[cursor++]
await updateOne(uid)
}
}
try {
const active = Math.min(PROFILE_UPDATE_WORKER_LIMIT, uids.length)
await Promise.allSettled(Array.from({ length: active }, worker))
} finally {
setLoadingProfiles(uids, false)
// 避免长时间批量更新后列表数据过晚刷新
void mutateProfiles()
}
},
[mutateProfiles, setLoadingProfiles],
)
const onUpdateAll = useLockFn(async () => {
const items = profileItems.filter((e) => e.type === 'remote')
const target = items
.map((item) => item.uid)
.filter((uid) => !loadingCache.has(uid))
setLoadingProfiles(target, true)
await runProfileUpdates(target)
})
const onCopyLink = async () => {
+1 -1
View File
@@ -6,7 +6,7 @@ const [ThemeModeProvider, useThemeMode, useSetThemeMode] = createContextState<
// save the state of each profile item loading
const [LoadingCacheProvider, useLoadingCache, useSetLoadingCache] =
createContextState<Record<string, boolean>>({})
createContextState<Set<string>>(new Set())
// save update state
const [UpdateStateProvider, useUpdateState, useSetUpdateState] =