C# Bitmap LockBits/UnlockBits in multi-thread

2019-08-27 18:02发布

问题:

I am working on a CCTV project which employs ONVIF. I use a Winform sample, which is provided by "ONVIF Device Manager" project, to obtain video frames from a camera. (You can find it here). I found that the sample put a CopyMemory() block code inside UI thread by using dispatcher.BeginInvoke(). I would slow down main UI thread because this block is repeated to display images in a 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
    }

I tried to exclude the CopyMemory() statement outside of UI thread by calling BeginInvoke() after UnlockBits() bitmap. But, an error is raised "Bitmap is already locked". There has one question which was posted, I have followed the answer of that question but another error occurs "Invalid parameter" while redrawing imageBox. I guess if we lock at bitmap lock(bmp) {CopyMemory();...} the imageBox cannot get information of bitmap associated with it.

Any help is highly appreciated.

Update Proposed Solution

        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();
            }

        });
    }

回答1:

You get the error "Bitmap is already locked" because of the following line:

Bitmap bmp = img as Bitmap;

It seems that img is declared globally, and it is being used both by your thread, and the UI thread ath the same time. When a Bitmap object is being displayed in UI, it is being Locked by UI thread for painting. The Lock method in your thread conflicts with this operation in UI thread.

To get better performance, I recommend you to generate a Bitmap for each frame you get in your thread. Then BeginInvoke it to display the prepared image. In UI thread you should care for disposing the Bitmap when replacing in PictureBox property.