【问题标题】:C# Bitmap LockBits/UnlockBits in multi-thread多线程中的 C# 位图 LockBits/UnlockBits
【发布时间】:2019-03-07 13:13:51
【问题描述】:

我正在从事一个采用 ONVIF 的闭路电视项目。我使用“ONVIF 设备管理器”项目提供的 Winform 示例从相机获取视频帧。 (你可以找到它here)。我发现该示例使用 dispatcher.BeginInvoke() 在 UI 线程中放置了 CopyMemory() 块代码。我会减慢主 UI 线程,因为重复此块以在 PictureBox 中显示图像。

void InitPlayback(VideoBuffer videoBuffer, bool isInitial)
    {
        //....

        var renderingTask = Task.Factory.StartNew(delegate
        {
            var statistics = PlaybackStatistics.Start(Restart, isInitial);
            using (videoBuffer.Lock())
            {
                try
                {
                    //start rendering loop
                    while (!cancellationToken.IsCancellationRequested)
                    {
                        using (var processingEvent = new ManualResetEventSlim(false))
                        {
                            var dispOp = disp.BeginInvoke((MethodInvoker)delegate
                            {
                                using (Disposable.Create(() => processingEvent.Set()))
                                {
                                    if (!cancellationToken.IsCancellationRequested)
                                    {
                                        //update statisitc info
                                        statistics.Update(videoBuffer);

                                        //render farme to screen
                                        //DrawFrame(bitmap, videoBuffer, statistics);
                                        DrawFrame(videoBuffer, statistics);
                                    }
                                }
                            });
                            processingEvent.Wait(cancellationToken);
                        }
                        cancellationToken.WaitHandle.WaitOne(renderinterval);
                    }
                }
                catch (OperationCanceledException error) { } catch (Exception error) { } finally { }
            }
        }, cancellationToken);
    }

    [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
    public static extern void CopyMemory(IntPtr dest, IntPtr src, int count);
    private void DrawFrame(VideoBuffer videoBuffer, PlaybackStatistics statistics)
    {
        Bitmap bmp = img as Bitmap;
        BitmapData bd = null;
        try
        {
            bd = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);//bgra32

            using (var md = videoBuffer.Lock())
            {

                CopyMemory(bd.Scan0, md.value.scan0Ptr, videoBuff.stride * videoBuff.height);

                //bitmap.WritePixels(
                //    new Int32Rect(0, 0, videoBuffer.width, videoBuffer.height),
                //    md.value.scan0Ptr, videoBuffer.size, videoBuffer.stride,
                //    0, 0
                //);
            }

        }
        catch (Exception err)
        {
            //errBox.Text = err.Message;
            Debug.Print("DrawFrame:: " + err.Message);
        }
        finally
        {
            bmp.UnlockBits(bd);
        }
        imageBox.Image = bmp;
        // var dispOp = disp.BeginInvoke((MethodInvoker)delegate {imageBox.Image = bmp;}); =>>Bitmap is already locked
    }

我试图通过在 UnlockBits() 位图之后调用 BeginInvoke() 来排除 UI 线程之外的 CopyMemory() 语句。但是,会引发错误“位图已锁定”。有 one question 已发布,我已按照该问题的答案进行操作,但在重绘 imageBox 时出现另一个错误“参数无效”。我想如果我们锁定位图 lock(bmp) {CopyMemory();...} imageBox 无法获取与其关联的位图信息。

非常感谢任何帮助。

更新建议的解决方案

        private void DrawFrame(PlaybackStatistics statistics)
    {
        Bitmap bmp = new Bitmap(videoBuff.width, videoBuff.height);//img as Bitmap;
        //...
        imageBox.Invoke((MethodInvoker)delegate
        {
            Image bmTemp = imageBox.Image;
            imageBox.Image = bmp;
            if (bmTemp != null)
            {
                bmTemp.Dispose();
            }

        });
    }

【问题讨论】:

  • 我可能会为此使用BackgroundWorker,因为您只是从 UI 线程卸载图像处理。
  • 您还应该将视频的读取和表单上的显示分开,而不是将 bmp 分配给 imageBox 的使用,引发一个刷新事件,该事件将由您的 imageBox OnPaint 事件接​​管绘制当前图像。
  • @LaurentLequenne,引发刷新事件将导致此issue
  • 我猜你的bmp可以通过从视频中获取新图像来锁定......所以你必须每次创建一个新图像,并将结果克隆到将重绘的全局图像在 onpaint 事件中。
  • 顺便说一句...从不imageBox.Image.Dispose()。您需要将imageBox.Image 的内容存储在一个单独的变量中,然后imageBox.Image 的内容更改为其他内容,然后然后从单独的变量。否则,在某个时刻,活动的 UI 元素将有一个已处置的对象链接到它,并且当它尝试重新绘制 UI 时,您将得到一个 ObjectDisposedException

标签: c# multithreading bitmap onvif lockbits


【解决方案1】:

由于以下行,您会收到错误“位图已锁定”:

Bitmap bmp = img as Bitmap;

img 似乎是全局声明的,它同时被您的线程和 UI 线程使用。当Bitmap 对象在 UI 中显示时,它被 UI 线程锁定以进行绘制。你线程中的Lock方法与UI线程中的这个操作冲突。

为了获得更好的性能,我建议您为线程中的每一帧生成一个位图。然后 BeginInvoke 显示准备好的图像。在 UI 线程中,您应该注意在 PictureBox 属性中替换时处理 Bitmap。

【讨论】:

  • 谢谢,我已经记住了您建议的解决方案。我希望我们可以使用“lock”或类似的东西来处理它来处理全局 img 变量。
  • 您可以在内部继续使用img 位图进行更新,但在显示时使用new Bitmap(bitmap) 构造函数进行克隆。而且,是的,出于记忆的原因,每次替换克隆时,您肯定需要处理它。
猜你喜欢
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 2017-08-01
  • 2012-12-04
  • 2015-06-28
  • 2011-07-23
  • 1970-01-01
  • 2021-11-19
相关资源
最近更新 更多