【问题标题】:Invoke Method never returns调用方法永不返回
【发布时间】:2019-11-13 22:08:45
【问题描述】:

我有一个线程正在运行,它从相机中抓取图像,保存它们,然后更新图片框以显示该位图。当我想退出应用程序时,我将 bool exitApplication 设置为 true,但它永远不会在 join() 调用中返回。

    public void GrabThread()
    {
        Bitmap copyBmp;
        while (!exitApplication)
        {
            mImage = m_camera.GrabImage();
            SaveImage();

            copyBmp = (Bitmap)mImage.bmp.Clone();
            if (pictureBox1.InvokeRequired)
            {
                pictureBox1.Invoke(new MethodInvoker(
                delegate ()
                {
                    pictureBox1.Image = copyBmp;
                }));
            }
            else
            {
                pictureBox1.Image = copyBmp;
            }
            m_camera.ReleaseImage();
        }
    }

我的退出代码:

        exitApplication = true;
        threadGrab.Join();
        m_camera.Stop();
        Close();

当我从 Debug 菜单调用 Break All 时,线程挂起

pictureBox1.Invoke(new MethodInvoker(
delegate ()
{
    pictureBox1.Image = copyBmp;
}));

为什么?我怎样才能防止这种情况发生?目前我必须在线程上调用 Abort() 才能退出它。

【问题讨论】:

  • Invoke 将在 UI 线程上执行它的调用。如果该线程很忙,它将阻塞。简单的解决方案是将后台与 UI 代码分离,并使用await 等待后台代码完成而不会阻塞,然后再返回 UI 线程。
  • 好的,我明白了,由于我在Button Clicked方法中,UI Thread正在等待。在等待时,永远不会调用调用,从而导致死锁。 Await() 做什么,或者更确切地说,如何使用它?我现在更改了我的代码,以便在抓取线程停止时使用 Environment.Exit(0)。
  • 相反,Invoke 调用,但 UI 线程已经被threadGrab.Join(); 阻塞。这会导致死锁 - 后台线程正在等待 UI 线程解除阻塞,但 那个 线程又在等待后台线程完成。
  • 至于如何修复代码,需要后台运行的部分是什么?

标签: c# multithreading delegates


【解决方案1】:

此代码死锁是因为pictureBox1.Invoke 试图在 UI 线程中执行代码。该线程虽然被调用 threadGrab.Join(); 阻塞了

这可以使用async/await 修复。 await 等待已经执行的异步任务,而不会像 Join() 那样阻塞。完成后,将在原始同步上下文中恢复执行。在 WinForms 或 WPF 应用程序中,这意味着在 UI 线程上恢复执行。

我假设唯一需要在后台运行的是m_camera.GrabImage();。在这种情况下,代码可能如下所示:

public async void Button1_Click(object sender, EventArgs args)
{
    await Task.Run(()=>m_camera.GrabImage();
    //Back in the UI thread
    SaveImage();
    var copyBmp = (Bitmap)mImage.bmp.Clone();
    pictureBox1.Image = copyBmp;
    m_camera.ReleaseImage();
}

Task.Run 将在线程池线程中执行m_camera.GrabImage()await 将等待这个完成而不阻塞 UI 线程。完成后,在 UI 线程中继续执行,这意味着不需要Invoke

这也可以循环运行:

public async void Capture_Click(object sender, EventArgs args)
{
    while(someCondition)
    {
        await Task.Run(()=>m_camera.GrabImage();

        SaveImage();
        var copyBmp = (Bitmap)mImage.bmp.Clone();
        pictureBox1.Image = copyBmp;
        m_camera.ReleaseImage();
    }
}

【讨论】:

  • 我把它改成了await System.Threading.Tasks.Task.Run(() => threadGrab.Join());,它工作了,应用程序将退出。不完全确定这整件事,但我没有收到任何错误。
  • 重点是您不需要需要额外的线程或Join()Task.Run 已经使用了不同的线程,没有理由使用 another 一个。 await 做了Join() 会做的事情,甚至更多,而不会阻塞 UI 线程。
【解决方案2】:

这可能不是正确的答案,但你可以同时使用 OnClosing 和 OnClosed 来停止你的相机然后关闭你的应用程序,我不知道它在 Winform 中是否有效,但它在 WPF 中对我有效

protected override void OnClosing(CancelEventArgs e)
{

            e.Cancel = false; //prevent your application to shutdown
            Camera.Stop(); //stop your camera
            base.OnClosing(e);

}

protected override void OnClosed(EventArgs e)
{
            base.OnClosed(e);
            Application.Current.Shutdown(); //then shutdown again
}

通过 2 步,您的相机将停止而无需加入任何线程。请注意,此代码是用 Wpf 编写的,而不是 Winform,但我认为您可以在 Winform 中找到类似的方法

希望这个答案对你有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-06
    相关资源
    最近更新 更多