I have created a event driven logger that seems to have issues when called from another thread. How can I make it thread safe?
Here is the LoggerClass:
public static class MyLogger
{
private static List<string> log = new List<string>();
public static event EventHandler LogAdded;
private static string indent = "";
public static void Log(string message)
{
log.Add(indent + message);
if (LogAdded != null)
LogAdded(null, EventArgs.Empty);
}
}
In my form class I initiated it in the following two ways:
public frm_main()
{
InitializeComponent();
MyLogger.LogAdded += new EventHandler(MyLogger_LogAdded);
}
And here is the event progress where the crash occurs:
private void MyLogger_LogAdded(object sender, EventArgs e)
{
int length = rtb_logger.TextLength; // at end of text
string prefix = string.Format("{0:d3}: ", ++logindex);
string ToAppend = prefix + MyLogger.GetLastLog();
rtb_logger.AppendText(ToAppend);
if (ToAppend.Contains("Alert -"))
{
rtb_logger.SelectionStart = length;
rtb_logger.SelectionLength = ToAppend.Length;
rtb_logger.SelectionColor = Color.Red;
}
rtb_logger.AppendText(Environment.NewLine);
rtb_logger.SelectionStart = rtb_logger.TextLength;
rtb_logger.ScrollToCaret();
EnableControls();
}
The crash occurs on the first line abobve commented with "at end of text" in the following call from the backgroundworker:
MyLogger.Log("Sending cmd: \"" + strCmdText + "\" to CMD.exe.");
How do I make MyLogger class thread-safe?
Edit: Going with SeeSharp solution, code changed is:
private void MyLogger_LogAdded(object sender, EventArgs e)
{
if (rtb_logger.InvokeRequired)
{
rtb_logger.BeginInvoke(new Action(delegate {
MyLogger_LogAdded(sender, e);
}));
return;
}
int length = rtb_logger.TextLength; // at end of text
string prefix = string.Format("{0:d3}: ", ++logindex);
string ToAppend = prefix + MyLogger.GetLastLog();
rtb_logger.AppendText(ToAppend);
if (ToAppend.Contains("Alert -"))
{
rtb_logger.SelectionStart = length;
rtb_logger.SelectionLength = ToAppend.Length;
rtb_logger.SelectionColor = Color.Red;
}
rtb_logger.AppendText(Environment.NewLine);
rtb_logger.SelectionStart = rtb_logger.TextLength;
rtb_logger.ScrollToCaret();
EnableControls();
}
You must call body of MyLogger_LogAdded in UI thread.
Try to use Dispatcher.BeginInvoke() when you call methods or properties of rtb_logger.
See C#: Thread safe richtextbox event logging method?
private delegate void MYLOGGEREVENT(object sender, EventArgs e);
private void MyLogger_LogAdded(object sender, EventArgs e)
{
if (InvokeRequired)
{
BeginInvoke(new MYLOGGEREVENT(MyLogger_LogAdded), new object[]{sender, e});
}
else{
int length = rtb_logger.TextLength; // at end of text
string prefix = string.Format("{0:d3}: ", ++logindex);
string ToAppend = prefix + MyLogger.GetLastLog();
rtb_logger.AppendText(ToAppend);
if (ToAppend.Contains("Alert -"))
{
rtb_logger.SelectionStart = length;
rtb_logger.SelectionLength = ToAppend.Length;
rtb_logger.SelectionColor = Color.Red;
}
rtb_logger.AppendText(Environment.NewLine);
rtb_logger.SelectionStart = rtb_logger.TextLength;
rtb_logger.ScrollToCaret();
EnableControls();
}
}
try this.
Related
I am writing a program which is supposed to detect when a USB serial device is plugged in and then log on to the new com port. The code below works wonderfully, but I have noticed in debugging the code and stepping through it that the event handler "DetectChange" fires twice. I'm not sure that this is normal, or an action of the debugger.
In any case, the code works, but I am new at event handling and I would like to make sure that I am not going to cause any issues as I add more code to actually read and write from the serial port.
(I got some of this code from stackoverflow, but I have misplaced my paper with names for attribution. If you see your code below, my heartfelt thanks.)
using System;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;
using System.Threading;
namespace SerialTest
{
public partial class Form1 : Form
{
SerialMethods serialMethods = new SerialMethods();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
loadCmdBox();
}
private void CmdBoxPort_SelectedIndexChanged(object sender, EventArgs e)
{
handleComPort();
}
private void handleComPort()
{
// Set the right port for the selected item.
// The portname is based on the "COMx" part of the string (SelectedItem)
string item = CmdBoxPort.SelectedItem.ToString();
// Search for the expression "(COM" in the "selectedItem" string
if (item.Contains("(COM"))
{
// Get the index number where "(COM" starts in the string
int indexOfCom = item.IndexOf("(COM");
// Set PortName to COMx based on the expression in the "selectedItem" string
// It automatically gets the correct length of the COMx expression to make sure
// that also a COM10, COM11 and so on is working properly.
string PortName = item.Substring(indexOfCom + 1, item.Length - indexOfCom - 2);
if (serialMethods._serialPort.IsOpen)
{
serialMethods._serialPort.Close();
serialMethods.Connect(PortName);
label5.Text = "Active Port: " + PortName;
}
else
{
serialMethods.Connect(PortName);
label5.Text = PortName;
}
}
else
return;
}
private void loadCmdBox()
{
// Get all serial (COM)-ports you can see in the devicemanager
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2",
"SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\"");
// Sort the items in the combobox
CmdBoxPort.Sorted = true;
// Add all available (COM)-ports to the combobox
foreach (System.Management.ManagementObject queryObj in searcher.Get().Cast<ManagementObject>())
{
CmdBoxPort.Items.Add(queryObj["Caption"]);
}
SerialPortService.PortsChanged += (sender1, changedArgs) => DetectChange(changedArgs.EventType);
label2.Text = "";
label3.Text = "";
label4.Text = "";
}
protected Task<Task> getSerPorts()
{
CmdBoxPort.Text = "";
CmdBoxPort.Update();
if (!String.IsNullOrEmpty(CmdBoxPort.Text))
{
handleComPort();
return Task.FromResult(Task.CompletedTask);
}
else
{
loadCmdBox();
return Task.FromResult(Task.CompletedTask);
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
SerialPortService.CleanUp();
this.Close();
}
private void RefreshButton_Click(object sender, EventArgs e)
{
refresh();
}
protected Task<Task> refresh()
{
label2.Text = "";
label3.Text = "";
label4.Text = "";
CmdBoxPort.Items.Clear();
getSerPorts();
return Task.FromResult(Task.CompletedTask);
}
protected virtual void DetectChange(EventType changedArgs)
{
if (changedArgs == EventType.Insertion)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at insertion invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
else if (changedArgs == EventType.Removal)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at removal invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
return;
}
}
public static class SerialPortService
{
private static SerialPort _serialPort;
private static string[] _serialPorts;
private static ManagementEventWatcher arrival;
private static ManagementEventWatcher removal;
private static readonly SerialMethods SD = new SerialMethods();
static SerialPortService()
{
_serialPorts = SerialPort.GetPortNames();
MonitorDeviceChanges();
}
public static void CleanUp()
{
arrival.Stop();
removal.Stop();
}
public static event EventHandler<PortsChangedArgs> PortsChanged;
private static void MonitorDeviceChanges()
{
try
{
var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
arrival = new ManagementEventWatcher(deviceArrivalQuery);
removal = new ManagementEventWatcher(deviceRemovalQuery);
arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(EventType.Insertion);
removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);
// Start listening for events
arrival.Start();
removal.Start();
}
catch (ManagementException err)
{
MessageBox.Show("Management exception = " + err, "Info", MessageBoxButtons.OK);
}
}
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = SerialPort.GetPortNames();
if (eventType == EventType.Insertion)
{
var added = availableSerialPorts.Except(_serialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
}
else if (eventType == EventType.Removal)
{
var removed = _serialPorts.Except(availableSerialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
}
}
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
{
handler?.Invoke(sender, args);
}
}
public enum EventType
{
Insertion,
Removal,
}
public class PortsChangedArgs : EventArgs
{
private readonly EventType _eventType;
private readonly string[] _serialPorts;
public PortsChangedArgs(EventType eventType, string[] serialPorts)
{
_eventType = eventType;
_serialPorts = serialPorts;
}
public string[] SerialPorts => _serialPorts;
public EventType EventType => _eventType;
}
}
Just took a short look at this. It seems like getSerPorts() will always execute loadCmdBox() (CmdBoxPort.Text = ""; ... if (!String.IsNullOrEmpty(CmdBoxPort.Text))) that will attach a new event handler (previous attached event handlers will not be removed by attaching a new one).
You should either remove the existing event handler befor attaching a new one or only attach the event handler once.
I would monitor data received on a Serial port with my pc and a Arduino.
On the arduino, the sketch send thorugt the USB the string "aabb" evry 300ms.
With pc I want listen, and in real time print the string in a control (Textbox). To do that, I create a new thread which listen in a Loop what arrives in Serial port, and when it happens it write by a Invoke the string in textbox. The procedures works if I deploy in the form's class but if I use a external class it doesn't. To explain better the matter, I paste the code of the class
class SerialPortManager
{
public SerialPort Serial = new SerialPort();
private Thread thr;
private string Log;
public TextBox textLog;
public string LastString;
public bool thrIsAlive;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[IODescriptionAttribute("ControlInvokeRequiredDescr")]
public bool InvokeRequired { get; private set; }
//DISPOSE
public void Dispose()
{
this.Dispose();
}
//SET Textobox LOG
public void SetLogTxtB (TextBox txt)
{
textLog = txt;
}
//PORTE DISPONIBILI
public string[] Available_Ports()
{
return SerialPort.GetPortNames();
}
//COSTRUTTORI
public SerialPortManager(string portname, int baudrate,bool InitializeConn)
{
Serial.BaudRate = baudrate;
Serial.PortName = portname;
if (InitializeConn == true) Serial.Open();
}
public SerialPortManager()
{
}
//SETTA I PARAMETRI E INIZIALIZZA LA CONNESSIONE
public void SetConnectionParam(string portname, int baudrate, bool initializeConn)
{
Serial.Close();
Serial.Dispose();
Serial = new SerialPort();
Serial.BaudRate = baudrate;
Serial.PortName = portname;
if (initializeConn == true) Serial.Open();
}
//ASYNC LISTENER
public void AsyncListener()
{
thrIsAlive = true;
thr = new Thread(ThreadReader);
thr.Start();
}
//PROCEDURA PER APPEND
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
textLog.Text += value;
}
private void Invoke(Action<string> action, params object[] v)
{
throw new NotImplementedException();
}
void ThreadReader()
{
while (thrIsAlive)
{
string temp = Serial.ReadLine();
LastString = temp;
Log += LastString + "\n";
AppendTextBox(LastString + "\n");
}
}
}
In the form I write three rows
SerialPortManager PortMan = new Driver_Arduin.SerialPortManager("COM3", 9600,true);
PortMan.SetLogTxtB(textBox1);
PortMan.AsyncListener();
If I try to run program it returns the error " cross-thread operation not allowed". Now, while I posting this ask, I decide to do a last try and change the method AppendTextBox to :
public void AppendTextBox(string value)
{
if (textLog.InvokeRequired)
{
try
{
textLog.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
catch (ObjectDisposedException)
{
thrIsAlive = false;
}
}
textLog.Text += value;
}
And It Finally works. Now ascertained the power of Stackoverflow that solved the problem before posting, I would know why my code works. Thank you
In SerialPortManager you must use delegate instead windows control.
class SerialPortManager
{
public SerialPort Serial = new SerialPort();
private Thread thr;
private string Log;
//public TextBox textLog;
public Action<string> textLog;
.....
Crete in you form simply method:
public void SetTextBoxText(string value)
{
if (textBox1.InvokeRequired)
{
try
{
textBox1.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
catch (ObjectDisposedException)
{
thrIsAlive = false;
}
}
textBox1.Text += value;
}
Set delegate to PortMan:
SerialPortManager PortMan = new Driver_Arduin.SerialPortManager("COM3", 9600,true);
PortMan.SetLogTxtB=new Action<string>(SetTextBoxText);
PortMan.AsyncListener();
If need output log to TextBox of PortMan call textLog delegate.
void ThreadReader()
{
while (thrIsAlive)
{
string temp = Serial.ReadLine();
LastString = temp;
Log += LastString + "\n";
//AppendTextBox(LastString + "\n");
textLog(LastString + "\n");
}
}
Apart from that your Invoke method in SerialPortManager should throw NotImplementedException the problem is that you define your own InvokeRequired/Invoke.
You need to use these methods provided by a WinForms control such that it knows whether your code is running inside the thread (UI thread) that created the control and how it can change context to this thread.
Actually it seems you may use your SerialPortManager but make use of InvokeRequired/Invoke of textLog like you're already doing in AppendTextBox.
BTW, if (initializeConn == true) is rather useless - if (initializeConn) is sufficient.
I'm having trouble figuring out what is wrong with my C# code.
I'm trying to learn how to use ConcurrentQueue class in System.Collections.Concurrent namespace.
In order to do this, I'm creating 2 instances of the same class in different threads, passing to the constructors a different Listbox control.
I am expecting each class instance of EventGenerator to raise events at random intervals, updating the Listbox the were passed with randomly generated number, and adding that number to a ConcurrentQueue which is also passed to the constructor.
In my main thread, is the method to DeQueue the ConcurrentQueue of objects EnQueued to it by both spawned threads.
But what I'm getting is the 2 EnQueue Listboxes displaying the same data and the DeQueue Listbox seeming reporting to have deQueued them both.
I apologize if my description is not good enough, and my code follows, along with a link to an image of my form in case it might better help visualize what I'm trying to do...
Form
public partial class Form1 : Form
{
ConcurrentQueue<int> CQ;
EventGenerator eventGenerator1;
EventGenerator eventGenerator2;
public Form1()
{
InitializeComponent();
CQ = new ConcurrentQueue<int>();
eventGenerator1 = new EventGenerator(CQ, listBox1);
eventGenerator1.OnRandomEvent += new EventGenerator.RandomEventHandler(RandomEvent);
eventGenerator2 = new EventGenerator(CQ, listBox2);
eventGenerator2.OnRandomEvent += new EventGenerator.RandomEventHandler(RandomEvent);
}
private void RandomEvent(object sender, IncomingConnectionEventArgs e)
{
string s = e.Property_Int.ToString()
+ " "
+ e.Property_String;
UpdateListbox(s, e.LB);
}
private void UpdateListbox(string argstring, ListBox argListBox)
{
if (InvokeRequired)
{
Invoke(new Action<string, ListBox>(UpdateListbox), new object[] { argstring, argListBox });
return;
}
int n;
bool b = false;
//do
//{
b = CQ.TryDequeue(out n);
//} while (!b);
argListBox.Items.Add(argstring);
argListBox.SelectedIndex = argListBox.Items.Count -1;
listBoxDeQueue.Items.Add(n.ToString());
listBoxDeQueue.SelectedIndex = listBoxDeQueue.Items.Count - 1;
}
private void button_Start_Click(object sender, EventArgs e)
{
Thread methodThread1 = new Thread(new ThreadStart(TheThread1));
methodThread1.Start();
Thread methodThread2 = new Thread(new ThreadStart(TheThread2));
methodThread2.Start();
}
private void TheThread2()
{
eventGenerator2.Start();
}
private void TheThread1()
{
eventGenerator1.Start();
}
private void button_Stop_Click(object sender, EventArgs e)
{
eventGenerator1.Stop();
eventGenerator2.Stop();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
eventGenerator1.Stop();
eventGenerator2.Stop();
}
}
IncomingConnectionEventArgs
class IncomingConnectionEventArgs : EventArgs
{
public System.Windows.Forms.ListBox LB;
public int Property_Int { get; set; }
public string Property_String { get; set; }
public IncomingConnectionEventArgs(int argInt, string argString,
System.Windows.Forms.ListBox lb)
{
LB = lb;
Property_Int = argInt;
Property_String = argString;
}
}
EventGenerator
class EventGenerator
{
public delegate void RandomEventHandler(
object sender,
IncomingConnectionEventArgs e);
public event RandomEventHandler OnRandomEvent;
public Random r = new Random();
public ListBox listBox;
public bool Generate = true;
public ConcurrentQueue<int> CQ;
public EventGenerator(ConcurrentQueue<int> argCQ, ListBox argListBox)
{
CQ = argCQ;
listBox = argListBox;
}
public void Start()
{
Generate = true;
while (Generate)
{
Thread.Sleep(r.Next(100, 2000));
RandomEvent();
}
}
public void Stop()
{
Generate = false; ;
}
public void RandomEvent()
{
if (OnRandomEvent == null)
{
return;
}
int n = r.Next(1000, 10000);
CQ.Enqueue(n);
IncomingConnectionEventArgs Args =
new IncomingConnectionEventArgs(n, "", listBox);
OnRandomEvent(this, Args);
}
}
The problem is with your use of Random. Unless you use a single instance of Random or explicitly seed each instance differently, the two threads calling Random.Next(...) will typically generate the same values.
A better way to seed your instance is this:
Random r = new Random(Guid.NewGuid().GetHashCode());
I'm trying to run the timer tick event which initially runs on my windows form,also when loaded on another class using a thread. I tried calling the timer event on another thread it didn't help. Am I supposed to use the same timer or create a new timer for that thread. This is my current implementation:
namespace XT_3_Sample_Application
{
public partial class Form1 : Form
{
Queue<string> receivedDataList = new Queue<string>();
System.Timers.Timer tmrTcpPolling = new System.Timers.Timer();
public Form1()
{
InitializeComponent();
TM702_G2_Connection_Initialization();
}
static void threadcal()
{
Class1 c = new Class1();
c.timer_start();
c.test("192.168.1.188",9999);
}
public string Connection_Connect(string TCP_IP_Address, int TCP_Port_Number)
{
if (tcpClient.Connected)
{
Socket_Client = tcpClient.Client;
TcpStreamReader_Client = new StreamReader(tcpClient.GetStream(), Encoding.ASCII);
tmrTcpPolling.Start();
Status = "Connected\r\n";
}
else
{
Status = "Cannot Connect\r\n";
}
}
public string Connection_Disconnect()
{
tmrTcpPolling.Stop();
// do something
return "Disconnected\r\n";
}
void TM702_G2_Connection_Initialization()
{
tmrTcpPolling.Interval = 1;
tmrTcpPolling.Elapsed += new ElapsedEventHandler(tmrTcpPolling_Elapsed);
}
#region Timer Event
void tmrTcpPolling_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
if (tcpClient.Available > 0)
{
receivedDataList.Enqueue(TcpStreamReader_Client.ReadLine());
}
}
catch (Exception)
{
//throw;
}
}
private void tmrDisplay_Tick(object sender, EventArgs e)
{
Tick();
}
public void Tick()
{
Console.Write("tick" + Environment.NewLine);
if (receivedDataList.Count > 0)
{
string RAW_Str = receivedDataList.Dequeue();
//tbxConsoleOutput.AppendText(RAW_Str + Environment.NewLine);
tbxConsoleOutput.AppendText(Parser_Selection(RAW_Str) + Environment.NewLine);
}
}
#endregion
private void btnConnect_Click(object sender, EventArgs e)
{
tbxConsoleOutput.AppendText(Connection_Connect(tbxTCPIP.Text, Convert.ToInt32(tbxPort.Text, 10)));
Thread t = new Thread(threadcal);
t.Start();
}
}
}
But the timer tick event starts the moment the application is launched but not on button click - private void btnConnect_Click(object sender, EventArgs e).
I'm trying to call a separate thread for the class Class1's test method. I'm trying to use a similar timer event to receive output from the server, for this thread.
namespace XT_3_Sample_Application
{
class Class1
{
TcpClient tcpClient;
Socket Socket_Client;
StreamReader TcpStreamReader_Client; // Read in ASCII
Queue<string> receivedDataList = new Queue<string>();
System.Timers.Timer tmrTcpPolling = new System.Timers.Timer();
void TM702_G2_Connection_Initialization()
{
tmrTcpPolling.Interval = 1;
tmrTcpPolling.Elapsed += new ElapsedEventHandler(tmrTcpPolling_Elapsed);
}
public void test(string TCP_IP_Address, int TCP_Port_Number)
{
TM702_G2_Connection_Initialization();
try
{
string Status = "";
Ping pingSender = new Ping();
PingOptions options = new PingOptions();
PingReply reply = pingSender.Send(TCP_IP_Address, timeout, buffer, options);
if (reply.Status == IPStatus.Success)
{
tcpClient = new TcpClient();
tcpClient.Connect(TCP_IP_Address, TCP_Port_Number);
if (tcpClient.Connected)
{
Socket_Client = tcpClient.Client;
TcpStreamReader_Client = new StreamReader(tcpClient.GetStream(), Encoding.ASCII);
tmrTcpPolling.Start();
Status = "Connected\r\n";
}
else
{
Status = "Cannot Connect\r\n";
}
}
else
{
Status = "Ping Fail\r\n";
}
MessageBox.Show(TCP_IP_Address + " :" + Status);
}
catch (System.Exception ex)
{
MessageBox.Show(TCP_IP_Address + " :" + ex.Message);
}
setFilterType();
setButtonRadioLvl();
heloCmd();
}
public void timer_Start()
{
Form1 f = new Form1();
f.Tick();
}
}
}
When tried the above method the timer is not fired on the new thread. Any suggestions on this?
Without any blocking code or loop your thread will not live long. the following calls your test method every one second and doesn't use timer
static void threadcal()
{
while (true)
{
Thread.Sleep(1000);
Class1 c = new Class1();
c.test("192.168.1.188", 9999);
}
}
I have a working solution that reports progress & text to a progress bar and a label on the applications's main form. I have now moved my worker methods to a class to they are accessible across multiple forms etc.
Within the worker methods are BW.ReportProgress() statements that push back the progress and text to the BackgroundWorker in the main form.
To give a better idea here is the file layout:
MainScreen.cs
List repSelected = new List();
XMLandRar xXMLandRar = new XMLandRar();
private void Rarbtn_Click(object sender, EventArgs e)
{
GetReps();
//Run worker
if (!CreateRarBW.IsBusy)
{
CreateRarBW.RunWorkerAsync();
}
}
//Worker
private void CreateRarBW_DoWork(object sender, DoWorkEventArgs e)
{
xXMLandRar.RarFiles(repSelected);
}
//Progress reporting
private void CreateRarBW_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progBar.Value = e.ProgressPercentage;
Statuslbl.Text = e.UserState.ToString();
}
Then my newly created Class that encompasses all of the worker methods and is to push progress to the main form.
XMLandRar.cs
public class XMLandRar
{
public void RarFiles(List repSelected)
{
int step = 100 / repSelected.Count();
int i = 0;
//Iterate through list and run rar for each
foreach (string rep in repSelected)
{
CreateRarBW.ReportProgress(i, "Raring files for " + rep);
DirectoryExists(rep);
ProcessRunner(rep);
i += step;
CreateRarBW.ReportProgress(i, "Raring files for " + rep);
}
}
}
The problem I am having is that in the XMLandRar class the CreateRarBW is not recognised (obviously) - how can I make a ReportProgress call to the BW in the main screen of the application?
Create an event in your XMLandRar class which you could subscribe to.
This way the XMLandRar class doesn't need to know or care about the UI or progressbar, it only cares about sending a message if anyone would listen. And there can also be more than one subscriber (let's say if you want to report to the background worker and a log, maybe)
Example:
private void Rarbtn_Click(object sender, EventArgs e)
{
GetReps();
//Run worker
if (!CreateRarBW.IsBusy)
{
// This should be done once, maybe in the contructor. Bind to new event.
xXMLandRar.ReportProgress += new EventHandler<XMLandRar.ProgressArgs>(xXMLandRar_ReportProgress);
CreateRarBW.RunWorkerAsync();
}
}
protected void xXMLandRar_ReportProgress(object sender, XMLandRar.ProgressArgs e)
{
// Call the UI backgroundworker
CreateRarBW.ReportProgress(e.Percentage, e.Message);
}
public class XMLandRar
{
// Event handler to bind to for reporting progress
public EventHandler<ProgressArgs> ReportProgress;
// Eventargs to contain information to send to the subscriber
public class ProgressArgs : EventArgs
{
public int Percentage { get; set; }
public string Message { get; set; }
}
public void RarFiles(List repSelected)
{
int step = 100 / repSelected.Count();
int i = 0;
//Iterate through list and run rar for each
foreach (string rep in repSelected)
{
// Report progress if somebody is listening (subscribed)
if (ReportProgress != null)
{
ReportProgress(this, new ProgressArgs { Percentage = i, Message = "Raring files for " + rep });
}
DirectoryExists(rep);
ProcessRunner(rep);
i += step;
// Report progress if somebody is listening (subscribed)
if (ReportProgress != null)
{
ReportProgress(this, new ProgressArgs { Percentage = i, Message = "Raring files for " + rep });
}
}
}
}
The sender object in the DoWork callback is the BackgroundWorker instance which is calling this callback.
This enables to use the instance and add it to your new XMLandRar class.
private void CreateRarBW_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker.
xXMLandRar.RarFiles(repSelected, worker);
}
XMLandRar.cs
public class XMLandRar
{
public void RarFiles(List repSelected, BackgroundWorker worker)
{
// ...
}
}
Or you set the BackgroundWorkerinstance as a class property to the XMLandRar.
public class XMLandRar
{
protected BackgroundWorker mWorker;
public XMLandRar(BackgroundWorker worker) {
mWorker = BackgroundWorker;
}
public void RarFiles(List repSelected)
{
// Do something with {mWorker}
}
}
Or as mentioned in the comments, using events in the XMLandRar class.
XMLandRar.cs
public class XmlandRarCompletedEventArgs : EventArgs
{
public readonly bool Finished;
public readonly bool Canceled;
public XmlandRarCompletedEventArgs(bool finished)
{
Finished = finished;
Canceled = !finished;
}
}public class OnXmlandRarUpdateEventArgs : EventArgs
{
public readonly int Percentage;
public readonly string Message;
public XmlandRarCompletedEventArgs(int perc) :
this(perc, "") {
}
public XmlandRarCompletedEventArgs(int perc, string message)
{
Percentage = perc;
Message = message;
}
}
public delegate void OnXmlandRarDoWorkHandler(object o);
public delegate void OnXmlandRarUpdateHandler(object o, OnXmlandRarUpdateEventArgs args);
public delegate void OnXmlandRarCompleteHandler(object o, XmlandRarCompletedEventArgs args);
public class XMLandRar
{
public event OnXmlandRarDoWorkHandler OnDoWork;
public event OnXmlandRarUpdateHandler OnUpdate;
public event OnXmlandRarCompletedHandler OnComplete;
public void RarFiles(List repSelected)
{
TriggerDoWork();
int step = 100 / repSelected.Count();
int i = 0;
//Iterate through list and run rar for each
foreach (string rep in repSelected)
{
TriggerUpdate(i, "Raring files for " + rep);
DirectoryExists(rep);
ProcessRunner(rep);
i += step;
TriggerUpdate(i, "Raring files for " + rep);
}
TriggerComplete(true);
}
private void TriggerDoWork()
{
if (OnDoWork != null) {
OnDoWork(this);
}
}
private void TriggerUpdate(perc) {
}
if (OnUpdate != null) {
OnUpdate(this, new OnXmlandRarUpdateEventArgs(perc));
}
private void TriggerUpdate(perc, string message)
{
if (OnUpdate != null) {
OnUpdate(this, new OnXmlandRarUpdateEventArgs(perc, message));
}
}
private void TriggerComplete(bool finished)
{
if (OnComplete != null) {
OnComplete(this, new XmlandRarCompletedEventArgs(finished));
}
}
}
Usage:
private void CreateRarBW_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
// Attach events to class
xXMLandRar.OnDoWork += delegate(object o) {
// ...
};
xXMLandRar.OnUpdate += delegate(object o, OnXmlandRarUpdateEventArgs args) {
// ...
};
xXMLandRar.OnComplete += delegate(object o, XmlandRarCompletedEventArgs args) {
// ...
};
xXMLandRar.RarFiles(repSelected, worker);
}
Hopefully without typos 'cause I'm in the office.
I have fixed errors in the code submitted and cleaned it up... This is a working sample that will help those that maybe couldn't understand the code since it was broken as it was... Although I would like to say that the intent and functionality of the code after it was cleaned up and enhanced is excellent.
This is working code that can get you started in your project for using a backGroundWorker thread for whatever you need.
Just modify this method -
public void RarFiles(List<string> repSelected)
To do whatever work you need. You will also have to modify the arguments you plan on using.. i.e. you may need a DataTable or some custom object...
You can modify the
public class OnXmlandRarUpdateEventArgs : EventArgs
For your needs.. that way when you get a callback.. you can update your main UI form with the changes made to those items..
You may need to do some tweaking.. but you see what i mean..
This is the Form Code.. Don't forget to create a button on the form...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ThreadSample
{
public partial class Form1 : Form
{
List<string> repSelected = new List<string>();
XMLandRar xXMLandRar = new XMLandRar();
BackgroundWorker CreateRarBW = new BackgroundWorker();
public Form1()
{
InitializeComponent();
repSelected = new List<string> { "asdf", "asdfsd", "h;ljj" };
CreateRarBW.DoWork += new DoWorkEventHandler(CreateRarBW_DoWork);
}
private void Rarbtn_Click(object sender, EventArgs e)
{
//GetReps();
//Run worker
if (!CreateRarBW.IsBusy)
{
// This should be done once, maybe in the contructor. Bind to new event.
xXMLandRar.ReportProgress += new EventHandler<XMLandRar.ProgressArgs>(xXMLandRar_ReportProgress);
CreateRarBW.RunWorkerAsync();
}
}
protected void xXMLandRar_ReportProgress(object sender, XMLandRar.ProgressArgs e)
{
// Call the UI backgroundworker
CreateRarBW.ReportProgress(e.Percentage, e.Message);
}
//private void CreateRarBW_DoWork(object sender, DoWorkEventArgs e)
//{
// var worker = sender as BackgroundWorker;
// xXMLandRar.RarFiles(repSelected, worker);
//}
private void CreateRarBW_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
// Attach events to class
xXMLandRar.OnDoWork += delegate(object o)
{
// ...
MessageBox.Show("Hey ... Something is going on over there in the classLib .. " + o);
};
xXMLandRar.OnUpdate += delegate(object o, OnXmlandRarUpdateEventArgs args)
{
// ...
//foreach (object oo in args)
{
MessageBox.Show("Hey ... Something is going on over there in the classLib .. Message is " + args.Message + " and Percentage is " + args.Percentage);
}
};
xXMLandRar.OnComplete += delegate(object o, XmlandRarCompletedEventArgs args)
{
MessageBox.Show("Hey ... Something is going on over there in the classLib .. Canceled is " + args.Canceled + " and Finished is " + args.Finished);
// ...
};
xXMLandRar.RarFiles(repSelected);//, worker);
}
}
}
This is the class code. You can just create a class in your current project... Keep in mind that the CreateRarBW object is a BackGroundWorker instance... (Wasn't included above..)
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;
namespace ThreadSample
{
public class XmlandRarCompletedEventArgs : EventArgs
{
public readonly bool Finished;
public readonly bool Canceled;
public XmlandRarCompletedEventArgs(bool finished)
{
Finished = finished;
Canceled = !finished;
}
}
public class OnXmlandRarUpdateEventArgs : EventArgs
{
public readonly int Percentage;
public readonly string Message;
public OnXmlandRarUpdateEventArgs(int perc) : this(perc, "")
{
}
public OnXmlandRarUpdateEventArgs(int perc, string message)
{
Percentage = perc;
Message = message;
}
}
public delegate void OnXmlandRarDoWorkHandler(object o);
public delegate void OnXmlandRarUpdateHandler(object o, OnXmlandRarUpdateEventArgs args);
public delegate void OnXmlandRarCompleteHandler(object o, XmlandRarCompletedEventArgs args);
public class XMLandRar // : BackgroundWorker
{
// Event handler to bind to for reporting progress
public EventHandler<ProgressArgs> ReportProgress;
// Eventargs to contain information to send to the subscriber
public class ProgressArgs : EventArgs
{
public int Percentage { get; set; }
public string Message { get; set; }
}
public event OnXmlandRarDoWorkHandler OnDoWork;
public event OnXmlandRarUpdateHandler OnUpdate;
public event OnXmlandRarCompleteHandler OnComplete;
public void RarFiles(List<string> repSelected)
{
TriggerDoWork();
int step = 100 / repSelected.Count();
int i = 0;
//Iterate through list and run rar for each
foreach (string rep in repSelected)
{
TriggerUpdate(i, "Raring files for " + rep);
//DirectoryExists(rep);
//ProcessRunner(rep);
i += step;
TriggerUpdate(i, "Raring files for " + rep);
}
TriggerComplete(true);
}
private void TriggerDoWork()
{
if (OnDoWork != null)
{
OnDoWork(this);
}
}
private void TriggerUpdate(int perc)
{
if (OnUpdate != null)
{
OnUpdate(this, new OnXmlandRarUpdateEventArgs(perc));
}
}
private void TriggerUpdate(int perc, string message)
{
if (OnUpdate != null)
{
OnUpdate(this, new OnXmlandRarUpdateEventArgs(perc, message));
}
}
private void TriggerComplete(bool finished)
{
if (OnComplete != null)
{
OnComplete(this, new XmlandRarCompletedEventArgs(finished));
}
}
}
}