I have a UserControl with a big table that is displaying values using a lot of converters. I am trying to display a ProgressBar in a new Window with Indeterminate State that is closing automatically when the UserControl Loaded event is fired.
This is the Thread creation in the backcode of my UserControl :
Thread progressBarThread = new Thread(new ThreadStart(delegate {
ProgressBarWindow progressBarWindow = new ProgressBarWindow();
progressBarWindow.IsIndeterminate = true;
progressBarWindow.LaunchProgressBarInBackground();
progressBarWindow.ShowDialog();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => { Dispatcher.FromThread(progressBarThread).InvokeShutdown(); };
This code is "working", it is opening progressBarWindow but when I shutdown the thread using InvokeShutdown (the ugliest way to do it, i agree). The problem is that the DoWork from my backgroundWorker.
Here is the DoWork function :
private void BackgroundWorker_WaitUntilShouldStop(object sender, DoWorkEventArgs e)
{
// Do not access the form's BackgroundWorker reference directly.
// Instead, use the reference provided by the sender parameter.
BackgroundWorker bw = sender as BackgroundWorker;
// Start the time-consuming operation.
while (!bw.CancellationPending)
{
Thread.Sleep(500);
}
}
I would like to call my function contained in ProgressBarWindow to stop the DoWork from runnning and close the ProgressBarWindow normaly using :
progressBar.StopProgressBarInBackground();
This method is calling backgroundWorker.CancelAsync();
This will result in backgroundWorker terminating and progressBarWindow closing automatically.
But I don't have access to progressBar that is inside the progressBarThread. I tried to pass my UserControl using :
progressBarThread.Start(this);
this being the main window.
When trying to pass a variable from the main thread, this error is thrown :
An exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll but was not handled in user code Additional information: The calling thread cannot access this object because a different thread owns it.
Does someone have a nice and correct way to do it without using myThread.InvokeShutdown() ?
Edit 1 :
I found a solution to my problem using a volatile variable :
volatile bool _isLoaded;
void CreateAndStopProgressBarWhenIsLoaded()
{
Thread progressBarThread= new Thread(new ThreadStart(
{
Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
progressBar.IsIndeterminate = true;
progressBar.LaunchProgressBarInBackground();
DispatcherTimer dispatcherTimer = new DispatcherTimer();
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += (sender, e) => {
if (_isLoaded)
progressBar.StopProgressBarInBackground();
};
// Try to stop `progressBar` every 500 ms
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
dispatcherTimer.Start();
progressBar.ShowDialog();
// Will only be reached once progressBar.ShowDialog(); returns
dispatcherTimer.Stop();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => {
_isLoaded = this.IsLoaded;
progressBarThread.Join(); // Wait for progressBarThread to end
};
}
Now the question is do you have a better solution ?
Edit 2 :
Here is my final solution thanks to #AlexSeleznyov :
void CreateAndStopProgressBarWhenIsLoaded()
{
Controls.ProgressBar.ProgressBar pb = null;
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Thread progressBarThread = new Thread(new ThreadStart(delegate
{
Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
pb = progressBar;
manualResetEvent.Set();
progressBar.IsIndeterminate = true;
progressBar.LaunchProgressBarInBackground();
progressBar.ShowDialog();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => {
pb.Dispatcher.Invoke(delegate {
manualResetEvent.WaitOne();
pb.StopProgressBarInBackground();
});
progressBarThread.Join();
};
}
You might try this approach, to cache ProgressBar instance and then use it from another thread. Dispatcher.Invoke eradicates need for CheckAccess I've mentioned in comments.
void CreateAndStopProgressBarWhenIsLoaded()
{
var pb = new Controls.ProgressBar.ProgressBar[1];
Thread progressBarThread= new Thread(new ThreadStart(
{
Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
pb[0] = progressBar;
progressBar.IsIndeterminate = true;
progressBar.LaunchProgressBarInBackground();
progressBar.ShowDialog();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => {
pb[0].Dispatcher.Invoke(()=>pb[0].Close());
};
}
I think that you can use BackgroundWorker.RunWorkerCompleted in ProgressBarWindow - it will be invoked when you cancel a backgroundWorker.
private void backgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
//close the window
}
}
Related
I have a MainForm and method which opens new window:
private void OpenWindow(object source, ElapsedEventArgs e)
{
var form = new SomeForm();
form.MdiParent = this;
form.Show();
}
And timer:
System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(OpenWindow);
timer.Interval = 10000;
timer.Enabled = true;
And it throws error on setting MdiParent: form.MdiParent = this;
Cross-thread operation not valid: Control 'MainForm' accessed from a
thread other than the thread it was created on.
How can I solve this problem?
You could use one of the other Timers because they handle the threading differently.
Explained here: Why there are 5 Versions of Timer Classes in .NET?
Based on Threading Model article, I think this should works in your cas:
var form = new SomeForm();
if (form.InvokeRequired)
{
form.Invoke(new MethodInvoker(delegate {
form.MdiParent = this;
}));
}
form.Show();
Or like this:
Invoke(new Action(() =>
{
form.MdiParent = this;
form.Show();
}));
Is it possible to add a callback to a background worker while it is running ?
bw.DoWork += new DoWorkEventHandler( some callback );
bw.RunWorkerAsync();
bw.DoWork += new DoWorkEventHandler( some callback );
Thank you.
Yes you can as it's only a subscription to an event but you can't run bw until he has completed the execution of the first task
here an example to illustrate this the following code will show an InvalidOperationException telling This BackgroundWorker is currently busy and cannot run multiple tasks concurrently."
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerAsync();
backgroundWorker1.DoWork+=new DoWorkEventHandler(backgroundWorker2_DoWork);
//at this line you get an InvalidOperationException
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
do
{
} while (true);
}
void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
do
{
} while (true);
}
}
As an answer to your comment question
#SriramSakthivel Thanks. Is there a way to put tasks in a queue ?
yes you can if you are using .net 4.0 you can use task with ContinueWith and attach it to your UI
taskScheduler it will have the same behavior as if you are using BackgroundWorker
private void TestButton_Click(object sender, EventArgs e)
{
TestButton.Enabled = false;
var uiThreadScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var backgroundTask = new Task(() =>
{
Thread.Sleep(5000);
});
var uiTask = backgroundTask.ContinueWith(t =>
{
TestButton.Enabled = true;
}, uiThreadScheduler);
backgroundTask.Start();
}
I am working on a MDI app where the user can create multiple instances of the same form (call it ListForm). Each instance of the ListForm has a flowlayoutpanel containing a unique set of user controls. The ListForm also contains a StatusStrip ProgressBar and a button called 'ReadAll'.
Each user control has a 'Read' button that will perform a read operation when clicked. This operation can take up to 3 seconds to complete.
What I am trying to do is when the user clicks the 'ReadAll' button, the childform spawns a background thread the iterates through the flowlayoutpanel.controls collection and invokes each user controls .PerformClick() method. This updates all the usercontrols in the form.
The problem is that it looks like the event handler for all instances of the form is being called resulting in all user controls in all instances of the ListForm are being updated. Additionally, when I ReportProgress from the backgroundworker, all the progressbars for all instances of the ListForm are updated. This functionality is not desired.
How can I ensure that only the ListForm that spawned the backgroundworker is updated? Is there a preferred way to uniquely identify the child form?
Thanks in advance for your help. Code is below...
public partial class ListForm: Form
{
// Background Worker Thread for Read / Write All tasks
private static BackgroundWorker bw = new BackgroundWorker();
public ListForm()
{
InitializeComponent();
// Configure the Background Worker that reads and writes all variable data
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void btnReadAll_Click(object sender, EventArgs e)
{
if (bw.IsBusy != true)
{
// Start the ReadAll parameters thread
btnReadAll.Text = "Cancel Read";
btnWriteAll.Enabled = false;
bw.RunWorkerAsync("R");
}
else if (bw.WorkerSupportsCancellation == true)
{
// Cancel the ReadAll parameters thread
bw.CancelAsync();
}
}
// ****************************** Background Thread Methods ***************************
public delegate void DoUIWorkHandler();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
DoUIWorkHandler DoReadClick;
DoUIWorkHandler DoWriteClick;
int CurrentControlCount = 1;
string StatusText = "";
int ProgressValue = 0;
string argument = e.Argument as string;
// *******************Perform a time consuming operation and report progress.
try
{
foreach (UserControl c in this.flowLayoutPanel1.Controls)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Update the status and return it to the UI
StatusText = "Updating: (" + (CurrentControlCount).ToString() + " of " + flowLayoutPanel1.Controls.Count.ToString() + ") " + c.ParamProperties.strDHIndexDescription;
ProgressValue = (int)(((float)CurrentControlCount / (float)flowLayoutPanel1.Controls.Count) * 100);
worker.ReportProgress(ProgressValue, StatusText);
System.Threading.Thread.Sleep(20);
CurrentControlCount++;
// Update the contorl
if (c.InvokeRequired)
{
if (argument == "R")
{
DoReadClick = c.btnRead.PerformClick;
c.Invoke(DoReadClick);
}
else
{
DoWriteClick = c.btnWrite.PerformClick;
c.Invoke(DoWriteClick);
}
}
}
}
}
catch(InvalidCastException ex)
{
// Catch any functions that are in the Layout panel
string ErrorStr = "Could not cast a Function control to a Parameter control. \n\r\r Exception: " + ex.Message;
srvcAppLogger.Logger.Log(new clsApplicationLogger.LoggerMessage(ErrorStr, "bw_DoWork", "frmVariableHandlerGUI"));
}
catch (Exception ex)
{
string ErrorStr = "An unecpected exception occured. Error: " + ex.Message.ToString();
srvcAppLogger.Logger.Log(new clsApplicationLogger.LoggerMessage(ErrorStr, "bw_DoWork", "frmVariableHandlerGUI"));
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tsStatus.Text = e.UserState.ToString();
this.tsProgressBar.Value = e.ProgressPercentage;
}
You've one instance of BackgroundWorker and each ListForm you create, is registered to this worker. So you've to pass the instance of the Form to the worker.
Create a little Helper class with two Attributes. This is just an example. You could also pass an identifier or what ever you like:
public struct ReadAllArguments
{
public bool Read;
public ListForm CallingForm;
public ReadAllArguments(bool read, ListForm callingForm)
{
Read = read; CallingForm = callingForm;
}
}
You could pass it then like this:
...
if (bw.IsBusy != true)
{
// Start the ReadAll parameters thread
btnReadAll.Text = "Cancel Read";
btnWriteAll.Enabled = false;
bw.RunWorkerAsync(new ReadAllArguments(true, this));
}
...
An later read it like that:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
DoUIWorkHandler DoReadClick;
DoUIWorkHandler DoWriteClick;
int CurrentControlCount = 1;
string StatusText = "";
int ProgressValue = 0;
ReadAllArguments arguments = e.Argument as ReadAllArguments;
if (this != arguments.ListForm)
return;
...
if (arguments.Read)
{
DoReadClick = c.btnRead.PerformClick;
c.Invoke(DoReadClick);
}
else
{
DoWriteClick = c.btnWrite.PerformClick;
c.Invoke(DoWriteClick);
}
...
You'll realize that you can even move the Work-Method out of you Form because there are no direct dependencies and you don't need access to the "this"-Qualifier. You've passed everything in you argument. After replacing every "this" by that argument you could register exactly one Work-Method to the DoWork-Event of your Worker. This would be much cleaner and more elegant...
Here's an example how you could do this:
public partial class ListForm: Form
{
// Background Worker Thread for Read / Write All tasks
private static BackgroundWorker bw = new BackgroundWorker();
static ListForm()
{
//We move the do-work out of the instance constructor, because the work that has to be done, is not connected to our instances. So we've only one definition of our work that has to be done
bw.DoWork += new DoWorkEventHandler(TheWorkThatHasToBeDone);
}
public ListForm()
{
InitializeComponent();
// Configure the Background Worker that reads and writes all variable data
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
//no more registering on instance level
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
//Your new instance-independent doWork-Method - static here
private static void TheWorkThatHasToBeDone(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
ReadAllArguments arguments = e.Argument as ReadAllArguments;
//You call the instance-Method here for your specific instance you want the work to be done for
arguments.ListForm.bw_DoWork(worker, arguments);
}
//Your old bw_DoWork-Method with nicer arguments - you should change the method name...
private void bw_DoWork(BackgroundWorker worker, ReadAllArguments arguments)
{
DoUIWorkHandler DoReadClick;
DoUIWorkHandler DoWriteClick;
int CurrentControlCount = 1;
string StatusText = "";
int ProgressValue = 0;
// *******************Perform a time consuming operation and report progress.
try
{
...
}
}
It would again be more elegant to move the stuff out of the forms code and not doing this with static members, but I think the idea is clear.
To identify object you could use HashCode or create an Id property, and next use it in custom EventArgs.
private Guid _controlId;
public ListForm()
{
_controlId = Guid.NewGuid();
...
}
Try also to menage the event observators in this way:
private void btnReadAll_Click(object sender, EventArgs e)
{
if (bw.IsBusy != true)
{
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged);
bw.RunWorkerCompleted +=bw_RunWorkerCompleted;
// Start the ReadAll parameters thread
btnReadAll.Text = "Cancel Read";
btnWriteAll.Enabled = false;
bw.RunWorkerAsync("R");
}
else if (bw.WorkerSupportsCancellation == true)
{
// Cancel the ReadAll parameters thread
bw.CancelAsync();
}
bw.DoWork -= bw_DoWork;
bw.ProgressChanged -= bw_ProgressChanged;
bw.RunWorkerCompleted -= bw_RunWorkerCompleted;
}
I have an application which i am add files to my Listbox and run those files.
My application play this files using PcapDot.Net project DLLs and send the packets through the network adapter.
The way is very simple: after all the files added to my application Listbox and the play button clicked the application handle the first file and after this file finished the next file began.
what i want to do is add control to my GUI that control the number of open thread in order to have the ability to play several file simultaneous.
This is my play button event:
private BackgroundWorker bw;
private void btnPlay_Click(object sender, EventArgs e)
{
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
manualResetEvent = new ManualResetEvent(false);
if (bw.IsBusy != true)
bw.RunWorkerAsync();
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < listBoxFiles.Items.Counti++) //run in loop all over my listbox
{
// here if have several wiresharkFile events that update my UI:
wiresharkFile.statusChangedEvent += new WiresharkFile.StatusChangedDelegate(
(wFile) =>
{
bw.ReportProgress(wiresharkFile.packetProgressPrecentage, wiresharkFile);
});
wiresharkFile.sendBuffer(); //play the file
}
}
What is the best way to add option to open more than 1 thread in the same time ?
here is a simple example for your use, it shows how to create and sign to an event you'll pop when the thread that open a file ends and then you can, when the event pop, to open another file. make sure you keep a counter and a lock so you won't have race conditions
public delegate void FileClosedHndlr();
public class MyThread
{
private event FileClosedHndlr FileClosed;
public void MyMain()
{
Thread t = new Thread(new ThreadStart(start));
FileClosed += new FileClosedHndlr(MyThread_FileClosed);
t.Start();
}
void MyThread_FileClosed()
{
// Thread has ended file open
// open another file
}
private void start()
{
// Open the file
// End thread
if (FileClosed != null)
{
FileClosed();
}
}
}
it took me a while, so use it
This case is using C# WPF. I want to instantly disable a button after clicking it to prevent clicking it twice in short succession. I disabled the button in OnClick_Event but still clickable.
Part of source is as below.
private void Button_Click_UpdateBurndownChart(object sender, RoutedEventArgs e)
{
if(threadNotWorking)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
private void startWorkThread()
{
... ...
//after finish required process
updateButton.IsEnabled = true;
}
Is there any way to accomplish this?
you may want to use a dispatcher, there is probably a threading problem (callback function running on seperate thread and trying to access ui which runs on another thread). try this . .
updateButton.Dispatcher.BeginInvoke(
new ThreadStart(() => updateButton.IsEnabled = false),
System.Windows.Threading.DispatcherPriority.Input, null);
instead of
updateButton.IsEnabled = false;
What happens if you were instead to change the order of your events from:
updateButton.IsEnabled = false;
startWorkThread();
To
startWorkThread();
updateButton.IsEnabled = false;
Let me know how this goes.
What it looks like is that you are starting your thread then immediatly enabling your button before your thread has finished. You would be better off using a BackgroundWorker and enable your Button in the RunWorkerCompleted Event. Though you can do something similar by enabling your button using a BeginInvoke at the end of your Process.
public void doWork()
{
System.Threading.Thread.Sleep(10000); //Simulating your Process
Dispatcher.BeginInvoke(new System.Threading.ThreadStart(delegate() { updateButton.IsEnabled = true; }), System.Windows.Threading.DispatcherPriority.Background);
}
Example with BackgroundWorker
using System.ComponentModel;
public partial class MainWindow : Window
{
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
updateButton.IsEnabled = true;
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(10000); //Simulating your work
}
private void startWorkThread()
{
bgw.RunWorkerAsync();
}
private void updateButton_Click(object sender, RoutedEventArgs e)
{
if (bgw.IsBusy != true)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
}