My program has to test several products in different slots simultaneously. When there's an error in a slot like accidental detachment from the computer, the program is suppose to log the error type and the serial number of the product that has been provided by the user when starting up the UI into a textfile.
I'm using Background Worker to handle the multi-threading. While I have managed to log the error type using e.Error, I can't seem to figure out how to pass the serial number from the DoWork function to the Background Worker error handler.
I tried googling for a solution but it seems like nobody has asked this before. I will really appreciate any help given.
PS: I'm quite new to C# so be gentle haha :)
Below is an example code:
private void startAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync();
}
}
private void cancelAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
// Cancel the asynchronous operation.
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
int b = 0; //simulate error
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
string[] array2 = { "1", "cancelled" };
e.Result = array2; //passing values when user cancel through e.Result object
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
worker.ReportProgress(i * 10, "Test a");
int a = 1 / b; //simulate error
System.Threading.Thread.Sleep(1000);
}
string[] array1 = {"1","done"};
e.Result = array1; //passing values when complete through e.Result object
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = e.ProgressPercentage.ToString() + "%" + e.UserState.ToString();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
string[] someArray2 = e.Result as string[];
string sernum = someArray2[0];
string status = someArray2[1];
resultLabel.Text = sernum + " " + status;
}
else if (e.Error != null)
{
resultLabel.Text = "Error: " + e.Error.Message; //how to pass sernum here?
}
else
{
string[] someArray = e.Result as string[];
string sernum = someArray[0];
string status = someArray[1];
resultLabel.Text = sernum + " " + status;
}
}
There are lots of different ways to get data back to the RunWorkerCompleted event handler in the case of an exception.
IMHO, the most natural from a semantic point of view is to put the data in the exception itself. For example:
class BackgroundWorkerException : Exception
{
public string Sernum { get; }
public BackgroundWorkerException(string sernum, Exception inner)
: base("DoWork event handler threw an exception", inner)
{
Sernum = sernum;
}
}
Then in your DoWork handler:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
try
{
int b = 0; //simulate error
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
string[] array2 = { "1", "cancelled" };
e.Result = array2; //passing values when user cancel through e.Result object
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
worker.ReportProgress(i * 10, "Test a");
int a = 1 / b; //simulate error
System.Threading.Thread.Sleep(1000);
}
string[] array1 = {"1","done"};
e.Result = array1; //passing values when complete through e.Result object
}
}
catch (Exception e)
{
throw new BackgroundWorkerException("1", e);
}
}
Finally, in the RunWorkerCompleted event handler:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
string[] someArray2 = e.Result as string[];
string sernum = someArray2[0];
string status = someArray2[1];
resultLabel.Text = sernum + " " + status;
}
else if (e.Error != null)
{
string sernum = ((BackgroundWorkerException)e.Error).Sernum;
resultLabel.Text = "Error: " + e.Error.Message;
}
else
{
string[] someArray = e.Result as string[];
string sernum = someArray[0];
string status = someArray[1];
resultLabel.Text = sernum + " " + status;
}
}
Your question isn't clear about what sernum actually represents, and in particular whether it's a single value for a given background task, or a single task could have more than one value for sernum. If it's the former, i.e. you know when you start the task what the value is, then you could pass it directly to the event handlers by capturing it in an anonymous method used for each actual event handler.
That approach won't work though in your specific scenario without some changes. You appear to have added a single BackgroundWorker object to your form as a component and are reusing it. Using an anonymous method works better/more easily if you are creating a new BackgroundWorker each time, so that you can subscribe your anonymous method delegate to DoWork and RunWorkerCompleted. (You have to subscribe it just before each invocation because, presumably, the sernum value is different each time.)
You could get it to work with the single component added to the form in the Designer as you're doing here, but it's a lot more complicated because you have to dynamically add a handler to the RunWorkerCompleted event which unsubscribes both itself and the delegates you subscribed to the DoWork and RunWorkerCompleted events (you wouldn't subscribe any methods directly to the component in the Designer, in this scheme).
Another alternative is to create a custom data structure passed as the argument for RunWorkerAsync(), which can contain a property for the sernum value. You can set this value in the method that starts the worker, or in the DoWork event handler.
This approach fits only a little better with the component-in-Designer scenario you have, because you still need a way to get the reference to that custom data structure back to the RunWorkerCompleted event handler, which you can do only by storing it in e.g. an instance field that can be shared between the Click event handler that starts the worker and the RunWorkerCompleted event (and frankly, if you do that, at that point it's debatable whether it's even worth it to pass that reference to the RunWorkerAsync() method, since the DoWork event handler could get at the same instance field just as well.)
Another alternative is to catch the exception as I've done in my code example above, but then instead of rethrowing the exception, treat it as if the work was cancelled (i.e. set the Result and Cancel properties).
Yet another approach is to abandon BackgroundWorker altogether and switch to the TPL Task-based idiom. That doesn't solve the problem implicitly, but it allows any of the above options, as well as the option of just defining your own mode for passing errors back.
If you need more specific help than that, you'll need to post a new question, with a good Minimal, Complete, and Verifiable code example that shows which of the above approaches you've attempted to try, or some other alternative not listed here, and what specifically you're unable to figure out.
See code below. You don't need a class. Can simple send a string or int using similar code.
public class Parameters
{
public string message = "";
}
private void startAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
Parameters parameters = new Parameters() { message = "The quick brown fox jumped over the lazy dog" };
backgroundWorker1.RunWorkerAsync(parameters);
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Parameters parameters = e.Argument as Parameters;
}
Related
I'm working a on project that continuously listens for packets on UDP port 514 (syslog). When a packet is received I need to parse the message so it can be displayed on my main form in a DataGridView control.
I have it so it updates the DataGridView with new messages, but I could get over 100 syslog messages for the same thing from my firewall so I want to search my dataGrid, see if the message already exists, and then update a "Hit count" column instead of making a new row for duplicate messages.
The problem is that I can't figure out how to invoke my DataGridView in a foreach loop. I'm not returning the value from the thread, I'm processing the data as it comes in, so I need to invoke the DataGridView control from the UI thread.
My code so far:
private async void Form1_Load(object sender, EventArgs e)
{
var udpCap = await Task.Run(() => captureSyslog());
}
public async Task<string> captureSyslog()
{
var listener = new UdpClient(514, AddressFamily.InterNetwork);
var ep = default(IPEndPoint);
while (true)
{
try
{
var data = listener.Receive(ref ep);
string incomingSyslog = Encoding.ASCII.GetString(data);
outputMessage(incomingSyslog);
}
catch (Exception ex2)
{
outputMessage(ex2.ToString());
}
}
}
private void outputMessage(string txt)
{
string[] chopped = txt.Split(',');
string descrip = chopped[32];
string severity = chopped[34];
string paloalto = chopped[59];
int rowIndex = -1;
foreach (DataGridViewRow row in syslogOutput.Rows)
{
try
{
if (row.Cells[2].Value.ToString().Equals(descrip) | row.Cells[1].Value.ToString().Equals(paloalto))
{
//Code to update DataGridView
}
}
catch (Exception ex3){ debugLabel1.Invoke(new Action(() => debugLabel1.Text = ex3.ToString()));}
}
if (syslogOutput.InvokeRequired)
{
syslogOutput.Invoke(new Action(() => syslogOutput.Rows.Add("1", chopped[59], chopped[34], chopped[32])));
}
}
When the code is run like this, I get a "System.NullReferenceException: Object reference not set to an instance of an object" which makes sense because the "syslogOuput" DataGridControl doesn't exist in the thread that's calling it. However the "syslogOutput.Invoke" line farther down works because it's being pulled in from the main UI thread.
Additionally, I could update my datagrid with the udpCap var in the Form1_load method, but when I do that I just get one message and the Task.Run thread quits.
So I guess my question can be phrased two ways:
How can I invoke a control in a foreach loop?
and/or
How can I get a return value from a task without ending the task that's returning the value?
UPDATE:
I dumped the Task and used the BackgroundWorker feature to capture the syslog messages. I abused the UserState in the ProgressChanged method to pass the syslog string so I can parse and then display it to my DataGridView natively in the UI thread. As far as I can tell the BackgroundWorker thread will run forever if there isn't a cancellation and/or the ReportProgress method never receives a "100" indicating that the process is 100% complete.
New code:
namespace ASA_SyslogCapture
{
public partial class Form1 : Form
{
BackgroundWorker bgWorker;
public Form1()
{
InitializeComponent();
bgWorker = new BackgroundWorker();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.ProgressChanged += new ProgressChangedEventHandler(bgWorker_ProgressChanged);
bgWorker.WorkerReportsProgress = true;
}
private async void Form1_Load(object sender, EventArgs e)
{
bgWorker.RunWorkerAsync();
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
//put udp code here
var listener = new UdpClient(514, AddressFamily.InterNetwork);
var ep = default(IPEndPoint);
while (true)
{
try
{
var data = listener.Receive(ref ep);
string incomingSyslog = Encoding.ASCII.GetString(data);
bgWorker.ReportProgress(0,incomingSyslog);
}
catch (Exception ex2) { debugLabel1.Text = ex2.ToString(); }
}
}
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
debugLabel1.Text = e.UserState as String; //this will eventually be my datagridview code
}
}
}
Huge thanks to this thread for the suggestion to use UserState to pass the string: C# backgroundWorker reports string?
I have been looking around for about 3 hours and can not get this invoke to work. I need the invoke because whats calling it is in a different thread and says its unstable.
Here's what I'm calling (I call it like this textBox1_TextChanged(null, null);):
private void textBox1_TextChanged(object sender, EventArgs e)
{
if(this.InvokeRequired)
{
this.Invoke(this?WHAT GOES HERE, null); // I know it should be a delegate or something but I can't change this to that
}
else
{
string temp = "";
temp += TextToAdd;
textBox1.Text = "s";
}
}
You can use BeginInvoke to update the UI from other Thread.
if (this.InvokeRequired)
{
var action = new Action(() => textBox1.Text = "s");
this.BeginInvoke(action);
}
I want to implement background worker in my windows application.
Currently I am using button event handler to load the combo box with data. As the query hangs the user interface, i would like to implement background worker as that the query runs in different thread. I have never used this background worker in any of my application. I did some research on this and still unable to implement this. Any help or advice will be greatly appreciated.
This is how my button event handler looks like
private void button6_Click(object sender, EventArgs e)
{
if (comboBox1.SelectedItem.ToString() == "All")
{
findAllUser();
}
else
{
//Do Something!!!
}
}
findAllUser() will fetch all the user from active directory which normally takes time and makes the UI unresponsive. Code for findAllUser() looks like this.
public void findAllUser()
{
System.DirectoryServices.DirectoryEntry entry = new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
System.DirectoryServices.DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user))";
foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
{
try
{
System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
}
catch (Exception)
{
// MessageBox.Show(e.ToString());
}
}
}
Below is how the background worker looks now..all empty
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
Any advice how can i implement the code above so that the background worker will populate the combobox2 with the active directory user list.
The easiest way would be to have your findAllUser method build a list of items that need to be added to the combo box, and then have the RunWorkerCompleted method populate the combo box. For example, modify your findAllUser like this:
private List<string> items;
public void findAllUser()
{
items = new List<string>();
System.DirectoryServices.DirectoryEntry entry =
new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
System.DirectoryServices.DirectorySearcher mySearcher =
new System.DirectoryServices.DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user))";
foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
{
try
{
System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
items.Add(de.Properties["GivenName"].Value.ToString() + " " +
de.Properties["sn"].Value.ToString() + " " + "[" +
de.Properties["sAMAccountName"].Value.ToString() + "]");
}
catch (Exception)
{
// MessageBox.Show(e.ToString());
}
}
}
Then, have your DoWork call findAllUser to do the actual work.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
findAllUser();
}
And finally, have your RunWorkerCompleted populate the combo box:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
foreach (var item in items)
{
comboBox2.Items.Add(item);
}
}
If you want to show progress, then you need to call ReportProgress from time to time while the worker is doing its business. Since you don't know exactly how long the process will take or exactly how many users it will find, you can't really report accurate progress. In that case, you have to guess. Since you say it takes "like 30 seconds," then you can use that as the 100% mark. So you start a StopWatch when the worker starts its processing, and update every half second or so. Modify your findAllUser function like this:
public void findAllUser()
{
const int ExpectedTime = 30000; // 30,000 milliseconds
// stopwatch keeps track of elapsed time
Stopwatch sw = Stopwatch.StartNew();
// Create a timer that reports progress at 500 ms intervals
System.Timers.Timer UpdateTimer;
UpdateTimer = new System.Threading.Timer(
null,
{
var percentComplete = (100 * sw.ElapsedMilliseconds) / ExpectedTime;
if (percentComplete > 100) percentComplete = 100;
ReportProgress(percentComplete);
// Update again in 500 ms if not already at max
if (percentComplete < 100)
UpdateTimer.Change(500, Timeout.Infinite);
}, 500, Timeout.Infinite);
items = new List<string>();
// rest of findAllUser here
// dispose of the timer.
UpdateTimer.Dispose();
}
And then, in your ProgressChanged event handler, update your progress bar.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update the progress bar with the value from e.ProgressPercentage
}
Again, because you don't know exactly how long things are going to take, you make an estimate. Above, I estimated 30 seconds, and the code blindly assumes that if 15 seconds have gone by, then it's half done.
Note that I create the timer as a one-shot and re-initialize it after every tick. I do this because I want to prevent concurrent updates. The timer fires on a separate thread, and the ReportProgress method marshals the call to the ProgressChanged event to the UI thread. If the UI thread is busy with other things, then another timer event could come in and you could end up with a bunch of threads all trying to marshal calls to the UI. Probably not a problem in this case because we're only talking a maximum of 60 calls (two calls per second for 30 seconds), but in general it's a good idea to prevent that kind of thing from happening.
Place your code on the backgroundWorker1_DoWork. But I suggest you either use Thread or Task Parallel Library
If you're on .NET 4.0, use the TPL.
You can do it like this:
Task runner = new Task(() =>
{
// do process here
});
runner.Start();
of if you're on older frameworks, use the Thread like this.
Thread thread = new Thread(() =>
{
// do process here
});
thread.IsBackground = true;
thread.Start();
read more about the TPL and Thread.
Using BackgroundWorker is convenient because it automagically invokes the ProgressChanged and RunworkerCompleted event handlers in the UI thread. You can use it like below.
private void AddItem(DirectoryEntry de)
{
comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
}
private void button6_Click(object sender, EventArgs e)
{
if (comboBox1.SelectedItem.ToString() == "All")
{
this.backgroundWorker1.RunWorkerAsync();
}
else
{
//Do Something!!!
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Bind to the users container.
DirectoryEntry entry = new DirectoryEntry("LDAP://CN=xyz,DC=com");
// Create a DirectorySearcher object.
DirectorySearcher mySearcher = new DirectorySearcher(entry);
try
{
// Create a SearchResultCollection object to hold a collection of SearchResults
// returned by the FindAll method.
SearchResultCollection result = mySearcher.FindAll();
int count = result.Count;
for(int i = 0; i < count; i++)
{
SearchResult resEnt = result[i];
try
{
DirectoryEntry de = resEnt.GetDirectoryEntry();
BeginInvoke(new Action<DirectoryEntry>(AddItem), de);
}
catch (Exception)
{
// MessageBox.Show(e.ToString());
}
this.backgroundWorker1.ReportProgress(i / count);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.progressBar1.Value = 100;
}
You can use below logic to implement background worker for your code.
var startListenerWorker = new BackgroundWorker();
startListenerWorker.DoWork += new DoWorkEventHandler(this.StartListenerDoWork);
startListenerWorker.RunWorkerAsync();
private void StartListenerDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
// Your logic to load comboBox will go here for running your query
}
you can also implement thread to have your logic run on a separate thread.
i call this after a button click:
FORM1-Code:
{
ProgressBar.Maximum = 500;
myArguments gifargs = new myArguments(); //class for passing arguments to the bgW
gifargs.InputFilePath = listBox1.SelectedItem.ToString(); // input filepath
gifargs.OutputFilePath = saveFileDialog1.FileName; //output filepath
backgroundWorker1.RunWorkerAsync(gifargs); // run bgW async with args
}
// here is bgW doWork
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
myArguments args = e.Argument as myArguments; //myArguments class
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
}
PictureHandler makeAnimatedGIf = new PictureHandler(); // creating new object
makeAnimatedGIf.imageGif(args.InputFilePath,args.OutputFilePath); //call method with args
makeAnimatedGIf.GifProgress += new PictureHandler.myprogressgetter(this.GifProgressF1);
//add the delegate
works perfect until here
this is my Callback function which should update bgW.ReportProgress
but it never gets there?!
private void GifProgressF1(int i)
{
System.Threading.Thread.Sleep(500);
backgroundWorker1.ReportProgress(i);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBar.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressBar.Value = 0;
if (e.Cancelled)
{
MessageBox.Show("Process canceled!");
}
else
{
MessageBox.Show("Process complete!");
}
}
<----Picturehandler.cs-Code----->
//Delegate definition in my Picturehandler class
public delegate void myprogressgetter(int i);
public myprogressgetter GifProgress;
public void imageGif(string input, string output)
{
Process imagemagick = new Process();
imagemagick.StartInfo.FileName = "convert.exe";
imagemagick.StartInfo.Arguments = "-monitor -delay 1 " + input + " +map " + output;
imagemagick.EnableRaisingEvents = false;
imagemagick.StartInfo.UseShellExecute = false;
imagemagick.StartInfo.CreateNoWindow = true;
imagemagick.StartInfo.RedirectStandardOutput = true;
imagemagick.StartInfo.RedirectStandardError = true;
imagemagick.Start();
StreamReader ima = imagemagick.StandardError;
bool assign2 = false;
do
{
string consolausgabe = ima.ReadLine();
if (consolausgabe.Contains("Reduce") == true)
{
assign2 = true;
}
gifprocess(consolausgabe, assign2);
} while (!ima.EndOfStream);
imagemagick.WaitForExit();
imagemagick.Close();
}
private void gifprocess(string cline, bool zähl)
{
if (cline.Contains("Load"))
{
string a1 = cline;
string[] a11 = a1.Split(new char[] { ':', ',' });
string a12 = a11[3];
string[] a13 = a12.Split(new char[] { '%' });
int load1 = Convert.ToInt32(a13[0]);
GifProgress(load1;) //<<<<<------------- this will give me an exception
// Visual Studio says GifProgress = null in Autos
}
now if i call GifProgress(100) or any other integer, I get exception(Object reference not set to an instance of an object.), progressbar gets never updated.
The progress information from the picturehandler class wont get to the UI, I tried for 2 days now.
I use same code to get textbox.text from form2 and callback function works just fine.
WorkerReportProgress = TRUE.
Typically, your DoWork method would have a loop in it. Each iteration of the loop could finish with a call to ReportProgress, which would cause the stuff in ProgressChanged to run.
Since you're just running a few lines of code, use the RunWorkerCompleted to set the progress indicator and forget ReportProgress altogether.
Here's a tutorial I used to understand the BackgroundWorker better, if it helps...
Unless maybe you're doing what you're doing because your background worker thread exits before makeAnimatedGIf.imageGif is finished doing whatever it does...
Make sure your BackgroundWorker's WorkerReportsProgress property is set to true. Default is false
Make sure to set WorkerReportsProgress to true and use Invoke() to update the progressbar from another thread.
The function that you expect to be called, GifProgressF1 is only referenced as a call back for an instance of `PictureHandler' class. Where and how this call back is called in entirely up to that class then. However from your description it is not clear where this class comes from or what it does. I'd suggest referring either to the class documentation or source code, to find exactly when this callback is supposed to be called and going from there.
This question already has answers here:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
(22 answers)
Closed 4 years ago.
In my windows form I have a text box and a button, the text box “tb_LogBox” is multiline text box I am trying to create a background worker that is supposed to call a function i.e. “LogTimer.DnT()” when I compile is and run it Visual studio throws InvalidOperationException.
The actual Error I am getting
Cross-thread operation not valid: Control 'tb_LogBox' accessed from a thread other than the thread it was created on. The following sample code illustrate what I am trying to do
private void button1_Click(object sender, EventArgs e)
{
try
{
var bw = new BackgroundWorker();
bw.DoWork += ExecuteOperations ;
bw.RunWorkerAsync();
}
catch (Exception ex)
{
tb_LogBox.AppendText(Environment.NewLine + " =#= " + ex.Message+" "+ex.Source);
}
}
private void ExecuteOperations(object sender, DoWorkEventArgs e)
{
var FuncCall = new LogTimer();
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DnT()); // the line i am getting the error. on
}
public class LogTimer
{
public string DnT()
{
const string datePat = #"d/MM/yyyy";
var dateTime = DateTime.Now();
return dateTime.ToString(datePat);
}
}
Try to use the begin invoke method :
BeginInvoke(new Action(() =>
{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DnT());
}));
This would be smoother than Invoke.
you need to marshall the Ui change onto the UI thread. This can be performed by using an invoke/begininvoke call around your tb_LogBox.AppendText
in a Winforms Application:
this.BeginInvoke((MethodInvoker)delegate
{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
in a WPF application:
this.Dispatcher.BeginInvoke(
(Action)delegate()
{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
Hope this helps!
Do this in your ExecuteOperations:
tb_LogBox.Invoke((MethodInvoker)delegate() { tb_LogBox.AppendText(...) }));
You cannot use other threads (BackgroundWorker uses a .NET threadpool thread) to make changes to UI components. This is a major hurdle you will have to get used to in WinForms programming.
The BackgroundWorker is executing on its own thread and all operations related to WinForms GUI elements must run on the thread they were created on. They way you currently use the BackgroundWorker is identical to just Queuing the operation using ThreadPool.QueueUserWorkItem(). For communication back to the GUI using a BackgroundWorker, use ReportProgess or set the DoWorkEventArgs.Result property in the worker method, and react to the corresponding events on the GUI thread. You can also use Invoke/BeginInvoke on a WinForms control to execute arbitrary code directly on the GUI thread. In your case that would mean replacing the line accessing the tb_LogBox with:
tb_LogBox.Invoke(new Action(() =>
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
));
You need to invoke the control's method on the UI thread:
private void ExecuteOperations(object sender, DoWorkEventArgs e)
{
var FuncCall = new LogTimer();
tb_LogBox.Invoke((MethodInvoker)delegate{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
}
I don't know what LogTimer does, but it may very well be that you should create that inside the delegate as well:
private void ExecuteOperations(object sender, DoWorkEventArgs e)
{
tb_LogBox.Invoke((MethodInvoker)delegate{
var FuncCall = new LogTimer();
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
}
You can't access the host thread from the background worker's execution thread. You can use the ReportProgress method of the BackgroundWorker to send information to the host thread.
private void button1_Click(object sender, EventArgs e)
{
try
{
var bw = new BackgroundWorker();
bw.DoWork += ExecuteOperations;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerAsync();
}
catch (Exception ex)
{
tb_LogBox.AppendText(Environment.NewLine + " =#= " + ex.Message + " " + ex.Source);
}
}
private static void ExecuteOperations(object sender, DoWorkEventArgs e)
{
var FuncCall = new LogTimer();
string text = Environment.NewLine + FuncCall.DnT();
(sender as BackgroundWorker).ReportProgress(0, text);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
tb_LogBox.AppendText(e.UserState as string);
}
public class LogTimer
{
public string DnT()
{
const string datePat = #"d/MM/yyyy";
var dateTime = DateTime.Now;
return dateTime.ToString(datePat);
}
}