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

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

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.

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).

Related

Cefsharp initializing for the second time throws error

My application needs to invoke (off screen) browser on request and cleanup once it is done.
So I created an offscreen browser
public class OffScreenBrowser
{
private static ChromiumWebBrowser _browser;
private static CefSettings _settings;
public void Load(string url,System.Drawing.Size size)
{
if (Cef.IsInitialized) return;
Init(new BrowserProcessHandler());
_browser = new ChromiumWebBrowser(url) {Size = size};
_browser.NewScreenshot += _browser_NewScreenshot;
}
public System.Windows.Controls.Image BrowserImage { get; set; }
public Action NewScreenShotAction { get; set; }
private void _browser_NewScreenshot(object sender, EventArgs e)
{
var bitmap = _browser.ScreenshotOrNull();
Application.Current.Dispatcher.Invoke(new Action(() =>
{
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
BrowserImage = new System.Windows.Controls.Image() {Source = bitmapImage};
NewScreenShotAction();
}
}));
}
public void Close()
{
_browser.NewScreenshot -= _browser_NewScreenshot;
_browser.Dispose();
_settings.Dispose();
Cef.Shutdown();
}
public static void Init(IBrowserProcessHandler browserProcessHandler)
{
_settings = new CefSettings();
if (!Cef.Initialize(_settings, true, browserProcessHandler))
throw new Exception("Unable to Initialize Cef");
}
}
The idea is-on clicking a button create browser and on clicking another button close the browser
private OffScreenBrowser offScreenBrowser;
private void OPen_OnClick(object sender, RoutedEventArgs e)
{
var address = #"https://www.google.co.uk";
var width = 200;
var height = 100;
var windowSize = new System.Drawing.Size(width, height);
offScreenBrowser = new OffScreenBrowser();
offScreenBrowser.Load(address, windowSize);
offScreenBrowser.NewScreenShotAction = () =>
{
Browser.Content = offScreenBrowser.BrowserImage;
};
}
private void Close_OnClick(object sender, RoutedEventArgs e)
{
offScreenBrowser.Close();
}
On the first click it all works fine. On clicking close it seems like the cleanup is fine.
But when I click the open button for the second time I am getting an exception as below
"An unhandled exception of type 'System.AccessViolationException' occurred in CefSharp.Core.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
What am I doing wrong?
This is a limitation in Chromium framework.
CEF can only be Initialized/Shutdown once per process,
See Need to Know/Limitations for more details, this is a limitation of the underlying Chromium framework.
to solve this you can call the Cef.Initialize in the start form and you need to call it only once in your application
the you can call ChromiumWebBrowser many times inside any for of your application forms
it worked for me

Delegate loads the Alert Form but I can't use any of the components.. its stuck

The problem is below. Here's my code...
// Contents of Form1.cs
// Usual includes
namespace ProcessMonitor
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public Boolean getStatus()
{
// Returns true if the system is active
if (label1.Text.Equals("Active"))
return true;
return false;
}
private void button1_Click(object sender, EventArgs e)
{
if(getStatus())
{
label1.Text = "Not Active";
button1.Text = "Activate";
}
else
{
label1.Text = "Active";
button1.Text = "Deactivate";
}
}
private void Form1_Load(object sender, EventArgs e)
{
Monitor mon = new Monitor(this);
mon.Run();
}
}
}
// Contents of Monitor.cs
// Usual includes
using System.Management;
using System.Diagnostics;
using System.Threading;
namespace ProcessMonitor
{
class Monitor
{
Form1 parent;
private void ShowAlert(Alert al)
{
al.Show();
}
public Monitor(Form1 parent)
{
this.parent = parent;
}
public void InvokeMethod()
{
//This function will be on main thread if called by Control.Invoke/Control.BeginInvoke
Alert frm = new Alert(this.parent);
frm.Show();
}
// This method that will be called when the thread is started
public void Run()
{
var query = new WqlEventQuery("__InstanceCreationEvent", new TimeSpan(0, 0, 0, 0, 1),
"TargetInstance isa \"Win32_Process\");
while (true)
{
using (var watcher = new ManagementEventWatcher(query))
{
ManagementBaseObject mo = watcher.WaitForNextEvent();a
//MessageBox.Show("Created process: " + ((ManagementBaseObject)mo["TargetInstance"])["Name"] + ",Path: " + ((ManagementBaseObject)mo["TargetInstance"])["ExecutablePath"]);
ManagementBaseObject o = (ManagementBaseObject)mo["TargetInstance"];
String str = "";
foreach (PropertyData s in o.Properties)
{
str += s.Name + ":" + s.Value + "\n";
}
this.parent.Invoke(new MethodInvoker(InvokeMethod), null);
}
}
}
}
}
Alert.cs is just a blank form with a label that says “new process has started”. I intend to display the name of the process and location, pid, etc. by passing it to this alert form via the Thread (i.e. class Monitor). I have deliberately made the thread load in form_load so that I can resolve this error first. Adding it as a thread properly after the main form loads fully is a later task. I need to fix this first..
The delegate creates the Alert form but I can’t click on it, its just stuck. Need help to solve this.
Your while loop in Run is blocking the UI thread.
by passing it to this alert form via the Thread
You never actually create a new thread or task here - you just run code which executes in the UI thread, and causes an infinite loop. This will prevent the main form, as well as your Alert form, from ever displaying messages.
You need to push this into a background thread in order for it to work, ie:
private void Form1_Load(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(_ =>
{
Monitor mon = new Monitor(this);
mon.Run();
});
}

Issues with loading winform for notification

I have created a small C# application to read some data from an event log and then display some results.
The program does a basic SQL query to get its initial data(this can take some time if querying several days worth of data), then it does some processing before it displays the results.
What I am trying to do is when the Submit button is pressed a message appears stating that it will take a few moments to retrieve and process the data. So, when the submit button is pressed I create a form with a message on it and display it.
This is code from the submit button and the associated methods:
private void btnSubmit_Click(object sender, EventArgs e)
{
DisplayCustomMessageBox("Please Wait");
ProcessRequest();
HideCustomMessageBox();
}
private void DisplayCustomMessageBox(string title)
{
CustomMessageBox = new frm_Message { Text = title };
CustomMessageBox.SetText("Please wait ");
CustomMessageBox.Show();
this.Enabled = false;
}
private void HideCustomMessageBox()
{
this.Enabled = true;
CustomMessageBox.Close();
}
Whats happening is that I have the form showing BUT the text in the form never displays. If I comment out the HideCustomMessageBox method the form displays without the text until the ProcessRequest method finishes. Then the form will finally display the text.
I assume its some sort of timing issue but I am not sure about how to fix it.
Thanks in advance.
Here is some threaded code to get you started.
private void btnSubmit_Click(object sender, EventArgs e)
{
DisplayCustomMessageBox("Please Wait");
Thread t = new Thread(()=>
{
ProcessRequest();
this.BeginInvoke(new Eventhandler((s,ee)=>{
HideCustomMessageBox();
}));
});
t.Start();
}
I'd do this using a modal dialog (Form.ShowDialog) and Task.Run to run ProcessRequest on a background thread. Async/await is very handy while implementing this.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsForms_21739538
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// test the btnSubmit_Click
this.Load += async (s, e) =>
{
await Task.Delay(2000);
btnSubmit_Click(this, EventArgs.Empty);
};
}
private async void btnSubmit_Click(object sender, EventArgs e)
{
try
{
// show the "wait" dialog asynchronously
var dialog = await DisplayCustomMessageBox("Please Wait");
// do the work on a pool thread
await Task.Run(() =>
ProcessRequest());
// close the dialog
dialog.Item1.Close();
// make sure the dialog has shut down
await dialog.Item2;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
// show a modal dialog asynchrnously
private async Task<Tuple<Form, Task<DialogResult>>> DisplayCustomMessageBox(string title)
{
//CustomMessageBox = new frm_Message { Text = title };
var CustomMessageBox = new Form();
CustomMessageBox.Text = "Please wait ";
var tcs = new TaskCompletionSource<bool>();
CustomMessageBox.Load += (s, e) =>
tcs.TrySetResult(true);
var dialogTask = Task.Factory.StartNew(
()=> CustomMessageBox.ShowDialog(),
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
// await the dialog initialization
await tcs.Task;
return Tuple.Create(CustomMessageBox, dialogTask);
}
void ProcessRequest()
{
// simulate some work
Thread.Sleep(2000);
}
}
}

Exception when closing Form (thread + invoke)

I have just started to learn about threads and methodinvoking in c#, but I have come across a problem which I couldn't find the solution of.
I made a basic C# form program which keeps updating and displaying a number, by starting a thread and invoke delegate.
Starting new thread on Form1_load:
private void Form1_Load(object sender, EventArgs e)
{
t = new System.Threading.Thread(DoThisAllTheTime);
t.Start();
}
Public void DoThisAllTheTime (which keeps updating the number) :
public void DoThisAllTheTime()
{
while(true)
{
if (!this.IsDisposed)
{
number += 1;
MethodInvoker yolo = delegate() { label1.Text = number.ToString(); };
this.Invoke(yolo);
}
}
}
Now when I click the X button of the form, I get the following exception:
'An unhandled exception of type 'System.ObjectDisposedException' occurred in System.Windows.Forms.dll
Can't update a deleted object'
While I actually did check if the form was disposed or not.
EDIT: I added catch (ObjectDisposedException ex) to the code which fixed the problem.
Working code:
public void DoThisAllTheTime()
{
while(true)
{
number += 1;
try {
MethodInvoker yolo = delegate() { label1.Text = number.ToString(); };
this.Invoke(yolo);
}
catch (ObjectDisposedException ex)
{
t.Abort();
}
}
}
Your call to this.IsDisposed is always out of date. You need to intercept your form closing event and stop the thread explicitly. Then you won't have to do that IsDisposed test at all.
There are many ways you can do this. Personally, I would use the System.Threading.Tasks namespace, but if you want to keep your use of System.Threading, you should define a member variable _updateThread, and launch it in your load event:
_updateThread = new System.Threading.Thread(DoThisAllTheTime);
_updateThread.Start();
Then in your closing event:
private void Form1_Closing(object sender, CancelEventArgs e)
{
_stopCounting = true;
_updateThread.Join();
}
Finally, replace the IsDisposed test with a check on the value of your new _stopCounting member variable:
public void DoThisAllTheTime()
{
MethodInvoker yolo = delegate() { label1.Text = number.ToString(); };
while(!_stopCounting)
{
number += 1;
this.Invoke(yolo);
}
}
Just put this override in your form class:
protected override void OnClosing(CancelEventArgs e) {
t.Abort();
base.OnClosing(e);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Thread.CurrentThread.Abort();
}

Multi threading in WPF using C# (with background worker)

I have written code to save an image which is generated by the application. The size of the image is around 32-35 MB. While saving the image to a BMB file, it is taking a long time, around 3-5 secs. For this purpose, I have used a background worker but when running the background worker, it shows an error like..."can't access the object as it is created on different thread".
Following is the code:
private void btnSaveDesign_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog sfd = new Microsoft.Win32.SaveFileDialog();
sfd.Title = "Save design as...";
sfd.Filter = "BMP|*.bmp";
if (sfd.ShowDialog() == true)
{
ww = new winWait();
ww.Show();
System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
fName = sfd.FileName;
cache = new CachedBitmap((BitmapSource)imgOut.Source, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
bw.RunWorkerAsync();
}
}
void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
ww.Close();
}
void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache)); //here... it says cant access...
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
I have declared "cache" as a global object. (A similar trick worked when I was programming in Windows Forms with VB.NET.)
ww is the wait window that I want to be displayed while the precess is being executed.
How to do this? Is there any other simple method for multi threading in WPF?
When WPF objects are created they are assigned to a Dispatcher object. This disallows any threads other than the creating thread to access the object. This can be circumvented by freezing the object by calling the freeze method. You would need to call Freeze on your bitmapsource object. Once you have frozen your object it becomes uneditable
Your problem comes about because you are accessing an object which is not created by the background worker thread. Normally this would happen if you access a UI control which is created in the main thread and accessed from different thread.
Use the code below.
Dispatcher.Invoke
(
new Action(
delegate()
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache));
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
)
);
I think you have to pass cache as a parameter to the new thread:
bw.RunWorkerAsync(cache);
and get it from the DoWork method:
var cache=(CacheType) e.Argument;
.NET framework provides a simple way to get started in threading with
the BackgroundWorker component. This wraps much of the complexity and
makes spawning a background thread relatively safe. In addition, it
allows you to communicate between your background thread and your UI
thread without doing any special coding. You can use this component
with WinForms and WPF applications. The BackgroundWorker offers
several features which include spawning a background thread, the
ability to cancel the background process before it has completed, and
the chance to report the progress back to your UI.
public BackgroudWorker()
{
InitializeComponent();
backgroundWorker = ((BackgroundWorker)this.FindResource("backgroundWorker"));
}
private int DoSlowProcess(int iterations, BackgroundWorker worker, DoWorkEventArgs e)
{
int result = 0;
for (int i = 0; i <= iterations; i++)
{
if (worker != null)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return result;
}
if (worker.WorkerReportsProgress)
{
int percentComplete =
(int)((float)i / (float)iterations * 100);
worker.ReportProgress(percentComplete);
}
}
Thread.Sleep(100);
result = i;
}
return result;
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
int iterations = 0;
if (int.TryParse(inputBox.Text, out iterations))
{
backgroundWorker.RunWorkerAsync(iterations);
startButton.IsEnabled = false;
cancelButton.IsEnabled = true;
outputBox.Text = "";
}
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
// TODO: Implement Cancel process
this.backgroundWorker.CancelAsync();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// e.Result = DoSlowProcess((int)e.Argument);
var bgw = sender as BackgroundWorker;
e.Result = DoSlowProcess((int)e.Argument, bgw, e);
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
workerProgress.Value = e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
outputBox.Text = "Canceled";
workerProgress.Value = 0;
}
else
{
outputBox.Text = e.Result.ToString();
workerProgress.Value = 0;
}
startButton.IsEnabled = true;
cancelButton.IsEnabled = false;
}

Categories

Resources