You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在主线程中等待函数终止?视频库回调同步问题求助

解决方案

1. 重构同步逻辑:用事件替代原子变量实现可靠同步

放弃TThread::Synchronize的隐式消息同步,改用手动事件+主线程主动轮询的方式,让库线程等待主线程完成任务,避免主线程阻塞。

首先在类中添加成员变量:

class TStreamToVideo : public TCustomControl
{
    // ... 原有成员 ...
    HANDLE FSyncEvent;         // 通知主线程有任务待处理
    HANDLE FCallbackDoneEvent; // 通知库线程任务已完成
    std::mutex FCallbackMutex; // 保护任务对象的线程安全
    std::function<void()> FSyncTask; // 待主线程执行的任务
};

2. 修改构造/析构函数初始化资源

TStreamToVideo::TStreamToVideo(TWinControl *Owner, const String &DllPath)
    : TCustomControl(Owner), Lib(DllPath.c_str()), Stream(NULL), 
      FSyncEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr)),
      FCallbackDoneEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr)),
      FSyncTask(nullptr)
{
    Parent = Owner;
    Align = alClient;
    Lib.Set(this->Handle);
    // 启动10ms定时器,让主线程主动检查任务
    SetTimer(Handle, 1, 10, nullptr);
}

TStreamToVideo::~TStreamToVideo()
{
    KillTimer(Handle, 1); // 先停止定时器
    Stop(); // 安全停止播放
    // 清理系统资源
    CloseHandle(FSyncEvent);
    CloseHandle(FCallbackDoneEvent);
}

3. 重写回调函数,用事件同步任务

以ReadCallback为例,修改为:

int TStreamToVideo::ReadCallback(void *data, unsigned char *buf, size_t len)
{
    int Result = -1;
    TStreamToVideo* Player = static_cast<TStreamToVideo*>(data);
    if (!Player) return Result;

    // 先检查停止标志,直接退出
    if (Player->StopCallbacks.load()) return -1;

    std::lock_guard<std::mutex> lock(Player->FCallbackMutex);
    Player->CallbacksActive.store(true);

    // 打包主线程要执行的任务
    Player->FSyncTask = [Player, &Result, buf, len]() {
        try {
            if (!Player->StopCallbacks.load())
                Result = Player->Stream->Read(buf, (System::NativeInt)len);
        } catch (...) {}
        // 任务完成后触发事件,通知库线程
        SetEvent(Player->FCallbackDoneEvent);
    };
    // 通知主线程有任务要处理
    SetEvent(Player->FSyncEvent);

    // 库线程等待主线程完成任务(这里不会阻塞主线程)
    WaitForSingleObject(Player->FCallbackDoneEvent.Handle, INFINITE);

    Player->CallbacksActive.store(false);
    return Result;
}

SeekCallback做相同修改即可。

4. 主线程处理任务:重写WndProc响应定时器

void TStreamToVideo::WndProc(TMessage &Msg)
{
    if (Msg.Msg == WM_TIMER && Msg.WParam == 1) {
        // 检查是否有等待处理的任务
        if (WaitForSingleObject(FSyncEvent, 0) == WAIT_OBJECT_0) {
            std::lock_guard<std::mutex> lock(FCallbackMutex);
            if (FSyncTask) {
                FSyncTask(); // 执行主线程任务
                FSyncTask = nullptr;
                ResetEvent(FCallbackDoneEvent); // 重置事件
            }
        }
    }
    TCustomControl::WndProc(Msg); // 处理原有消息
}

5. 安全的停止流程

修改Stop()函数,确保等待回调完成后再停止库:

void TStreamToVideo::Stop(void)
{
    StopCallbacks.store(true);

    // 等待回调完成,最多等待1秒避免死锁
    DWORD waitResult = WaitForSingleObject(FCallbackDoneEvent, 1000);
    if (waitResult == WAIT_OBJECT_0 || !CallbacksActive.load()) {
        Lib.Stop();
    } else {
        // 极端情况强制停止,可添加日志记录
        MessageBox(NULL, L"回调未及时完成,已强制停止", L"提示", 0);
        Lib.Stop();
    }
}

关键改进说明

  • 让库线程等待主线程完成任务,而非主线程等待库线程,彻底避免主线程阻塞死锁
  • 用事件替代原子变量,实现可靠的任务完成通知,解决CallbacksActive状态不一致的问题
  • 主线程通过定时器主动处理任务,比TThread::Synchronize的隐式消息更可控,避免CheckSynchronize()无效的问题

内容的提问来源于stack exchange,提问作者Peter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 15:57:01