I have attached a backgroundWorker component to my main form which runs a parallel task of capturing the screen for an animated gif. The worker's function has a while loop that runs until I use CancelAsync() on the worker, at which point it exits the loop, does some other things like saving the gif file and so on and returns some results to the UI thread.
private bool capturing = false;
public MainForm()
{
InitializeComponent();
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.WorkerSupportsCancellation = true;
}
private void captureBtn_Click(object sender, EventArgs e)
{
Debug.WriteLine("Button clicked");
if (capturing) { return; }
if (!backgroundWorker.IsBusy)
{
backgroundWorker.RunWorkerAsync();
}
}
private void stopCaptureBtn_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy)
{
backgroundWorker.CancelAsync();
}
}
private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
capturing = true;
Debug.WriteLine("DoWork running");
while (!backgroundWorker.CancellationPending)
{
Debug.WriteLine("Capturing frame {0}", frames);
//do the capturing to memory stream
}
Debug.WriteLine("DoWork cancelled");
//do some other things like saving the gif etc
e.Result = someResult;
}
private void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
Debug.WriteLine("RunWorkerCompleted running");
capturing = false;
//does something with the e.Result
}
My console output during a normal test is something like this:
Button clicked
DoWork running
Capturing frame 0
Capturing frame 1
Capturing frame 2
Capturing frame 3
Capturing frame 4
Capturing frame 5
Cancel button clicked
DoWork cancelled
The thread 0x2e4c has exited with code 0 (0x0).
DoWork running
DoWork cancelled
The thread 0x1010 has exited with code 0 (0x0).
RunWorkerCompleted running
The function seems to be running twice, I can see 2 separate threads exiting and also I don't seem to get any results from capturing. If I set a breakpoint inside the backgroundWorker_DoWork function and continue later, the first run does the capturing normally. What could be going on?
Its getting called twice because binding the event second time right after InitializeComponent().
Just comment those lines and it should work fine.
Here's the same example without the issue of running twice.
Sample Output
...
...
...
Capturing frame 2632
Capturing frame 2633
Capturing frame 2634
DoWork cancelled
RunWorkerCompleted running
public partial class Form1 : Form
{
private bool capturing = false;
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerSupportsCancellation = true;
// Don't need to re-bind
//backgroundWorker1.DoWork += backgroundWorker1_DoWork;
//backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
//backgroundWorker1.WorkerSupportsCancellation = true;
}
private void captureBtn_Click(object sender, EventArgs e)
{
Debug.WriteLine("Button clicked");
if (capturing) { return; }
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}
private void stopCaptureBtn_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy)
{
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
capturing = true;
Debug.WriteLine("DoWork running");
int frames = 1;
while (!backgroundWorker1.CancellationPending)
{
Debug.WriteLine("Capturing frame {0}", frames);
//do the capturing to memory stream
frames++;
}
Debug.WriteLine("DoWork cancelled");
//do some other things like saving the gif etc
//e.Result = someResult;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.WriteLine("RunWorkerCompleted running");
capturing = false;
//does something with the e.Result
}
The thread 0x3108 has exited with code 0 (0x0) means there was no error.
While reading or writing the big data, you should divide it into parts. Otherwise, you cannot progress. Your current while loop is disabled when writing to memory stream.
so your backgroundWorker_DoWork method should be like this :
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
// this is an example file
string filePath = #"C:\file.gif";
// it determines how many bytes of data will be received from the stream each time.
byte[] buffer = new byte[4096];
int byteCount = 0;
// we are preparing to read stream.
using (FileStream fs = new FileStream(filePath, FileMode.Open, System.IO.FileAccess.Read))
using (MemoryStream ms = new MemoryStream())
{
// read the stream bytes and fill buffer
while ((byteCount = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// if the task was canceled
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
// write buffer to memory stream
ms.Write(buffer, 0, byteCount);
}
}
}
Related
I have a lengthily processing in my winform when I click a button; namely, i'm loading lots of files and processing them. For the duration of the processing, my GUI is frozen and unresponsive which is a problem since the processing can take an upward of 10 minutes. Is there a way of putting the code in some sort of bubble or something so I can use the GUI while processing the files? Maybe even add the "Cancel" button.
EDIT: René's solution works, also here's progressbar control I also wanted:
private async void button1_Click(object sender, EventArgs e)
{
progressBar1.Maximum = ValueWithTOtalNumberOfIterations.Length;
IProgress<int> progress = new Progress<int>(value => { progressBar1.Value = value;});
await Task.Run(() =>
{
var tempCount = 0;
//long processing here
//after each iteration:
if (progress != null)
{
progress.Report((tempCount));
}
tempCount++;
}
}
You could simply make your button's click handler async and start a Task for your long running operation:
public async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false; // disable button to not get called twice
await Task.Run(() =>
{
// process your files
}
button1.Enabled = true; // re-enable button
}
The compiler turns this into a state machine. The control flow is returned to the caller (the UI) at the await keyword. And execution of this method is resumed when your Task has completed.
To use a "Cancel"-Button you can use a TaskCancellationSource or simply define a flag that you check while you're processing your files, and return if the flag is set (by the click handler of your "Cancel" button):
private bool _stop = false;
private void cancelButton_Click(object sender, EventArgs e)
{
_stop = true;
}
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false; // disable button to not get called twice
_stop = false;
await Task.Run(() =>
{
// process your files
foreach(var file in files)
{
if (_stop) return;
// process file
}
}
button1.Enabled = true; // re-enable button
}
I've created a small program. Everthing works fine but now I have recognized that I have to wait for an comport input and here I stuck .
Here is my DataReceived thread:
void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
RxString = serialPort.ReadLine();
this.Invoke(new EventHandler(DisplayText));
}
void DisplayText(object sender, EventArgs e)
{
String RxString1 = RxString+("\n");
if (RxString1 == "END\n") {
stopauto = "stop";
autostart.Enabled = true;
autostop.Enabled = false;
}
Here the if with end and other things works fine.
But now I have another thread, inside which I have to wait for an comport input to receive , eg: "go".
I don't post the whole code it has near 200 lines ...
private void AutostartClick(object sender, EventArgs e)
{
.... Do some Code
... Here i want to wait for "go"
... If go is received go ahead
}
I have tried it with a while and while and a if ... but this will not work because inside the while I receive nothing from the comport .
Then played around with AutoResetEvent , but it stucks too.
What am I doing wrong?
I would do the following: As the DataReceived event of the SerialPort component is executed in a separate thread, you are safe to assume that it will be executed even though your main thread waits.
This allows you to set a WaitHandle if the required "keyword" is received, and you can wait for this event to occur:
private ManualResetEvent _goReceived = new ManualResetEvent(false); // Initialize as "not set"
void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
RxString = serialPort.ReadLine();
// DO NOT CALL DISPLAYTEXT IF YOU RECEIVE "GO" - THE MAIN THREAD IS BLOCKED!
if (RxString.StartsWith("go"))
{
_goReceived.Set();
}
else
{
this.Invoke(new EventHandler(DisplayText));
}
}
void DisplayText(object sender, EventArgs e)
{
String RxString1 = RxString+("\n");
if (RxString1 == "END\n") {
stopauto = "stop";
autostart.Enabled = true;
autostop.Enabled = false;
}
}
In your method that should wait for "go", just wait for the event to occur:
private void AutostartClick(object sender, EventArgs e)
{
.... Do some Code
// Wait for "go" and THEN call DisplayText instead of calling
// it above
_goReceived.Wait();
DisplayText();
// Reset the event to make this works more than just once
_goReceived.Reset();
... If go is received go ahead
}
I have found a solution ... maybe not the best way but it works.
With ManualResetEvent or AutoResetEvent it always freeze.
I think the problem was that i was in a while loop and so the wait handler was never signaled so it freeze at this point.
Now i work with a global variable, a simple if in the DisplayText event and a while which calls this.Invoke(new EventHandler(DisplayText)); and it works without any problems.
Here is the Serialport and DisplayText Code:
String waitvar = ""; // Global wait Variable
void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
RxString = serialPort.ReadLine();
this.Invoke(new EventHandler(DisplayText));
}
void DisplayText(object sender, EventArgs e)
{
String RxString1 = RxString+("\n");
if (RxString1 == "go\n") {
waitvar = "go";
}
comview.AppendText(RxString1);
}
And at the Wait point:
private void AutostartClick(object sender, EventArgs e)
{
.... Do some Code
// Wait here
while (waitvar != "go")
{
this.Invoke(new EventHandler(DisplayText));
}
.... Do some Code
}
Thank you to all helpers !
I am trying to read data from my USB buffer. However when I read data from the buffer I have to perform some operation on this data, and this takes time. In the mean time the external device observes that the buffer is full and stops writing data. I need to overcome this situation. THe processing involves reading the array and some UI operations , so I though I could put the entire operation of reading data into a background thread and read the data once the background thread has completed operation
BackgroundWorker bw = new BackgroundWorker();
// this allows our worker to report progress during work
bw.WorkerReportsProgress = true;
// what to do in the background thread
bw.DoWork += new DoWorkEventHandler(
delegate(object o, DoWorkEventArgs args)
{
BackgroundWorker b = o as BackgroundWorker;
// do some simple processing for 10 seconds
for (int i = 1; i <= 10; i++)
{
// report the progress in percent
b.ReportProgress(i * 10);
Thread.Sleep(1000);
}
//I have to keep reading from the buffer here
});
// what to do when progress changed (update the progress bar for example)
bw.ProgressChanged += new ProgressChangedEventHandler(
delegate(object o, ProgressChangedEventArgs args)
{
label1.Text = string.Format("{0}% Completed", args.ProgressPercentage);
});
// what to do when worker completes its task (notify the user)
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
delegate(object o, RunWorkerCompletedEventArgs args)
{
label1.Text = "Finished!";
//I have to use the data from the buffer here
});
bw.RunWorkerAsync();
But even with this approach I still am not reading the buffer continously since only after the operation on the UI completes can I call the background worker again. Is there any way to continously read the buffer(like into a large 2d array) and keep notifying me when each 1d array of the buffer is full? The main idea is the task in the worker should not be stopped, however I still need to get the data from the worker task(know that it has completed). What would be the best way to achieve this?
Maybe in your case using the following class would come handy :
public class BackgroundTimer : BackgroundWorker
{
private ManualResetEvent intervalManualReset;
private enum ProcessStatus { Created, Running, JobCompleted, ExceptionOccured };
private ProcessStatus processStatus = new ProcessStatus();
public int Interval { get; set; }
public BackgroundTimer()
{
this.processStatus = ProcessStatus.Created;
this.WorkerSupportsCancellation = true;
this.Interval = 1000;
}
protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
{
base.OnRunWorkerCompleted(e);
if (processStatus == ProcessStatus.ExceptionOccured)
// Log : Process stopped;
processStatus = ProcessStatus.JobCompleted;
}
protected override void OnDoWork(DoWorkEventArgs e)
{
while (!this.CancellationPending)
{
try
{
base.OnDoWork(e);
this.Sleep();
}
catch (Exception exception)
{
//Log excepption;
this.processStatus = ProcessStatus.ExceptionOccured;
this.Stop();
}
}
if (e != null)
e.Cancel = true;
}
public void Start()
{
this.processStatus = ProcessStatus.Running;
if (this.IsBusy)
return;
this.intervalManualReset = new ManualResetEvent(false);
this.RunWorkerAsync();
}
public void Stop()
{
this.CancelAsync();
this.WakeUp();
this.Dispose(true);
}
public void WakeUp()
{
if (this.intervalManualReset != null)
this.intervalManualReset.Set();
}
private void Sleep()
{
if (this.intervalManualReset != null)
{
this.intervalManualReset.Reset();
this.intervalManualReset.WaitOne(this.Interval);
}
}
public void Activate()
{
if (!this.IsBusy)
// Log : Process activated.
this.Start();
}
}
EDIT :
Usage :
private void InitializeThread()
{
var timer = new BackgroundTimer();
timer.Interval = 1000; // sleep 1 second between each iteration.
timer.DoWork += timer_DoWork;
timer.Start();
}
void timer_DoWork(object sender, DoWorkEventArgs e)
{
// your desired operation.
}
I hope it helps.
when i trying to use webBrowser component inside the backgroundWorker.DoWork function , i got this exception :
System.InvalidCastException was unhandled by user code
http://i.stack.imgur.com/EJFT3.jpg
here's my code :
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
//NOTE : Never play with the UI thread here...
string line;
//time consuming operation
while ((line=sr.ReadLine()) != null ){
int index = line.IndexOf(":");
HtmlDocument doc = web.Document;
Thread.Sleep(1000);
m_oWorker.ReportProgress(cnt);
//If cancel button was pressed while the execution is in progress
//Change the state from cancellation ---> cancel'ed
if (m_oWorker.CancellationPending)
{
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
cnt++;
}
//Report 100% completion on operation completed
m_oWorker.ReportProgress(100);
}
or is their another way to use thread in c# ?
cause when i use Thread.sleep method in the main the gui freezes !!
The WebBrowser doesn't like being accessed from other threads. Try passing it in to RunWorkerAsync() like this:
private void button1_Click(object sender, EventArgs e)
{
HtmlDocument doc = web.Document;
m_oWorker.RunWorkerAsync(doc);
}
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
HtmlDocument doc = (HtmlDocument)e.Argument;
//NOTE : Never play with the UI thread here...
string line;
//time consuming operation
while ((line = sr.ReadLine()) != null)
{
int index = line.IndexOf(":");
Thread.Sleep(1000);
m_oWorker.ReportProgress(cnt);
//If cancel button was pressed while the execution is in progress
//Change the state from cancellation ---> cancel'ed
if (m_oWorker.CancellationPending)
{
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
cnt++;
}
//Report 100% completion on operation completed
m_oWorker.ReportProgress(100);
}
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;
}