Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

InvalidOperationException while saving a bitmap and using graphics.copyFromScreen parallel-y

I am writing an application to capture the screen using the CopyFromScreen method, and also want to save the image I capture to send over my local network. So, I am trying store the captured screen on one bitmap, and save another bitmap, which is the previously captured screen, on two threads.

However, this is throwing an InvalidOperationException, which says object is currently in use elsewhere. The exception is thrown by System.Drawing.dll.
I have tried locking, and am using separate bitmaps for saving and capturing the screen. How do I stop this from happening? Relevant code:

Bitmap ScreenCapture(Rectangle rctBounds)
{
    Bitmap resultImage = new Bitmap(rctBounds.Width, rctBounds.Height);

    using (Graphics grImage = Graphics.FromImage(resultImage))
    {
        try
        {
            grImage.CopyFromScreen(rctBounds.Location, Point.Empty, rctBounds.Size);
        }
        catch (System.InvalidOperationException)
        {
            return null;
        }
    }
    return resultImage;
}

void ImageEncode(Bitmap bmpSharedImage)
{
    // other encoding tasks
    pictureBox1.Image = bmpSharedImage;
    try
    {
        Bitmap temp = (Bitmap)bmpSharedImage.Clone();
        temp.Save("peace.jpeg");
    }
    catch (System.InvalidOperationException)
    {
        return;
    }
}

private void button1_Click(object sender, EventArgs e)
{
    timer1.Interval = 30;
    timer1.Start();
}

Bitmap newImage = null;

private async void timer1_Tick(object sender, EventArgs e)
{
    //take new screenshot while encoding the old screenshot 

    Task tskCaptureTask = Task.Run(() =>
        {
            newImage = ScreenCapture(_rctDisplayBounds);
        });
    Task tskEncodeTask = Task.Run(() =>
            {
                try
                {
                    ImageEncode((Bitmap)_bmpThreadSharedImage.Clone());
                }
                catch (InvalidOperationException err)
                {
                    System.Diagnostics.Debug.Write(err.Source);
                }
            });

    await Task.WhenAll(tskCaptureTask, tskEncodeTask);

    _bmpThreadSharedImage = newImage;
}

img1img2

like image 524
Priyank Avatar asked Jul 27 '17 09:07

Priyank


2 Answers

I reproduced your problem in a nutshell by creating a simple winforms project with a single button on it.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Task.Run(() => SomeTask());
    }

    public void SomeTask() //this will result in 'Invalid operation exception.'
    {
        var myhandle = System.Drawing.Graphics.FromHwnd(Handle);
        myhandle.DrawLine(new Pen(Color.Red), 0, 0, 100, 100);
    }
}

In order to fix this you need to do the following:

public partial class Form1 : Form
{
    private Thread myUIthred;

    public Form1()
    {
        myUIthred = Thread.CurrentThread;
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Task.Run(() => SomeTask());
    }

    public void SomeTask() // Works Great.
    {
        if (Thread.CurrentThread != myUIthred) //Tell the UI thread to invoke me if its not him who is running me.
        {
            BeginInvoke(new Action(SomeTask));
            return;
        }
        var myhandle = System.Drawing.Graphics.FromHwnd(Handle);
        myhandle.DrawLine(new Pen(Color.Red), 0, 0, 100, 100);
    }

}

The issue is (as Spektre implied) a result of trying to call a UI method from a non-UI thread. The 'BeginInvoke' is actually `this.BeginInvoke' and 'this' is the form which was created by the UI thread and therefore all works.

like image 161
G.Y Avatar answered Nov 01 '22 13:11

G.Y


I do not code in C# so I may be wrong here but I assume you are using Windows...

Accessing any visual components (like GDI Bitmap or window ...) is not safe outside WndProc function. So if you are using GDI bitmap (bitmap with device context) or rendering/accessing any visual component from your window inside any thread then there is your problem. After that any call to WinAPI in your app can throw an exception (even unrelated to graphics)

So try to move any such code into your WndProc function. In case you do not have access to it use any event of your window (like OnTimer or OnIdle).

like image 31
Spektre Avatar answered Nov 01 '22 13:11

Spektre