Get ip adresses on a lan with backgroundworker control - c#

I want to get alive or dead ip addresses from a big lan. but it takes so many times. I decided to use backgroundworker here is my code:
try
{
this.Status.Text = "Collecting Information...";
if(this.TxtWorkGroup.Text.Trim() == "")
{
MessageBox.Show("The Work Group name Should Not be Empty");
return;
}
// Use Your work Group WinNT://&&&&(Work Group Name)
DirectoryEntry DomainEntry = new DirectoryEntry("WinNT://" + this.TxtWorkGroup.Text.Trim());
DomainEntry.Children.SchemaFilter.Add("computer");
// To Get all the System names And Display with the Ip Address
foreach(DirectoryEntry machine in DomainEntry.Children)
{
string[] Ipaddr = new string[3];
Ipaddr[0] = machine.Name;
System.Net.IPHostEntry Tempaddr = null;
try
{
Tempaddr = (System.Net.IPHostEntry)Dns.GetHostByName(machine.Name);
}
catch(Exception)
{
//MessageBox.Show("Unable to connect woth the system :" + machine.Name );
deadHostList.Items.Add(machine.Name);
continue;
}
System.Net.IPAddress[] TempAd = Tempaddr.AddressList;
foreach(IPAddress TempA in TempAd)
{
Ipaddr[1] = TempA.ToString();
byte[] ab = new byte[6];
int len = ab.Length;
// This Function Used to Get The Physical Address
int r = SendARP( (int) TempA.Address, 0, ab, ref len );
string mac = BitConverter.ToString( ab, 0, 6 );
Ipaddr[2] = mac;
}
System.Windows.Forms.ListViewItem TempItem = new ListViewItem(Ipaddr);
this.ListHostIP.Items.Add(TempItem);
}
this.Status.Text = "Displayed";
}
catch(Exception ex)
{
MessageBox.Show(ex.Message,"Error",System.Windows.Forms.MessageBoxButtons.OK );
Application.Exit();
}
but when I try to use these codes in backgroundWorker1_DoWork event it gives me error messages
Cross-thread operation not valid: Control 'deadHostList' accessed from a thread other than the thread it was created on
How can I modify my codes?

As Chris and Reed stated....you can only modify a Ui control from the thread that the control was created on...
You can also use the BackgroundWorker's ProgressChanged event for Ui updates....
var worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
/// runs on background thread
worker.DoWork += (s, e) =>
{
while (!done)
{
DoSomeWork();
// send a message to the Ui
// via the ProgressChanged event
worker.ReportProgress(percent, statusMessage);
}
};
/// the ProgressChanged event runs on the UI thread
worker.ProgressChanged += (s, e) =>
{
var msg = (MyStatusMessage)e.UserState;
someUiControl.Items.Add(msg.Text);
};
/// also runs on Ui thread
worker.RunWorkerCompleted += (s, e) =>
{
};
worker.RunWorkerAsync();

You cannot and should not access UI controls from any thread other than the thread that created the control. I guess your deadHostList is a ListView control or something similar.
You can marshal a request from the background thread to the UI thread by using Control.Invoke or Control.BeginInvoke

You need to always marshal calls to UI elements, such as your list control, onto the UI thread.
You can do this via Control.Invoke. Change this:
deadHostList.Items.Add(machine.Name);
To:
string name = machine.Name;
deadHostList.Invoke(new Action( () => deadHostList.Items.Add(name)));
You'll also need to do the same thing later for ListHostIP - make sure to use Control.Invoke to wrap that call as well.

Related

Showing busy indicator on a STA thread

I have a long operation wehre I'd like to show the Extended Toolkits busy indicator. I made a previous post about this and it was fixed Wpf Extended toolkit BusyIndicator not showing during operation. However, during that call I have to interact with a UI element (canvas) and I get a "The calling thread must be STA, because many UI components require this". I understand (now) that a background worker(see code):
private void CboItemId_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
BackgroundWorker _backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
ItemSearchBusyIndicator.IsBusy = true;
// Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
if (RdoItemSearch.IsChecked == false) return;
///backgroundWorker_DoWork(null, null);
if (CboItemId.SelectedValue == null) return;
if (CboItemId.SelectedValue.ToString() != string.Empty)
{
selectedItem = CboItemId.SelectedValue.ToString();
_backgroundWorker.RunWorkerAsync();
}
// Mouse.OverrideCursor = System.Windows.Input.Cursors.Arrow;
}
public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
LoadItemData(selectedItem);
}
uses MTA and cannot be set to STA. So i tried calling the internal function that uses the UI elelment in its own thread:
public void LoadItemData(string itemId)
{
Axapta ax = new Axapta();
files.Clear();
try
{
ax.Logon(Settings.Default.Server, null, Settings.Default.Test, null);
AxaptaContainer path = (AxaptaContainer)ax.CallStaticClassMethod(Settings.Default.ClassName, Settings.Default.ItemData, itemId);
for (int i = 1; i <= path.Count; i++)
{
AxaptaContainer somestring = (AxaptaContainer)path.get_Item(i);
for (int j = 1; j <= somestring.Count; j += 2)
{
string extension = Path.GetExtension(somestring.get_Item(j + 1).ToString().ToLower());
if (extension == ".jpg"
|| extension == ".jpeg"
|| extension == ".gif"
|| extension == ".png"
|| extension == ".bmp"
|| extension == ".pdf")
/* key=path - value=description */
files.Add(somestring.get_Item(j + 1).ToString(), somestring.get_Item(j).ToString());
}
}
// _canvas.Children.Clear();
Thread t = new Thread(new ThreadStart(LoadPictures));
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
ax.Logoff();
}
}
Heres where I interact with the canvas element:
private void LoadPictures()
{
foreach (DictionaryEntry filePath in files)
{
try
{
Picture p = new Picture();
ToolTip t = new ToolTip();
t.Content = filePath.Value;
p.ToolTip = t;
TextBlock tb = new TextBlock();
tb.Text = filePath.Value.ToString();
Canvas.SetTop(tb, y);
Canvas.SetLeft(tb, x);
p.ImagePath = filePath.Key.ToString();
p.OriginalImagePath = filePath.Key.ToString();
p.ImageName = filePath.Value.ToString();
_canvas.Children.Add(p); //<-------This is where i seem to error
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message,"File Load Error",MessageBoxButton.OK,MessageBoxImage.Error);
}
}
}
but I get a "The calling thread cannot access this object because a different thread owns it"
I don't know how to call the long running (LoadItemData()) function while showing the BusyIndicator without a backgroundworker. Any help appreciated
There are multiple approaches:
1) Async binding, it's not recommended, but it is there. You can run long running task in property getter, framework will prevent UI from blocking, when it is finished - UI will get updated.
2) Use BackgroundWorker or Task/Thread to run code, but invoke it into UI thread. In your example:
Dispatcher.InvokeAsync(() => _canvas.Children.Add(p));
3) You can block UI thread of main window completely, no problems. But to indicate about its being busy you can create window in another thread and show there busy status (run animations, etc):
var thread = new Thread(() =>
{
var window = new SomeWindow();
window.ShowDialog();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();

Background Worker's ReportProgress for an Asynchronous task

I am using a BackgroundWorker to send asynchronous HTTP requests (with RestSharp by the way) and need to pass the returned data (HTTP response) to the main thread to update some GUI components based on that data. For this I use ReportProgress method like this:
static void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Object[] args = (Object[])e.Argument;
string strURL = args[0].ToString();
string strParam = args[1].ToString();
for (int i=0; i<5; i++) {
var client = new RestClient(strURL);
var request = new RestRequest(strURL, Method.POST);
request.AddParameter("someparam", strParam);
client.ExecuteAsync(request, response =>
{
string strRet = response.Content;
worker.ReportProgress(i, strArr);
}
}
}
This code will raise an exception on ReporProgress saying that it is illegal to call this method when the Background Worker has already finished its work and it is absolutely correct because by the time I receive HTTP response the bw_DoWork will already have finished executing.
So as you can see I am dealing with an asynchronous task in already asynchronous Background Worker thread.
I know that ReportProgress is marshaled to execute on GUI thread and I don't see any other ways to pass data back to the main form. How can this be fixed/corrected?
You need some way to let your background worker sleep/wait until all ExecuteAsync´s are finished.
You can do it like this.
void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Object[] args = (Object[])e.Argument;
string strURL = args[0].ToString();
string strParam = args[1].ToString();
int finishedCounter = 0;
// A reset event that is set from the last ExecuteAsync
AutoResetEvent finishedEvent = new AutoResetEvent(false);
for (int i=0; i<5; i++) {
var client = new RestClient(strURL);
var request = new RestRequest(strURL, Method.POST);
request.AddParameter("someparam", strParam);
client.ExecuteAsync(request, response =>
{
string strRet = response.Content;
worker.ReportProgress(i, strArr);
// Check if this is the last worker/step then
// wake up the background worker to finish the do_work method
if (Interlocked.Increment(ref finishedCounter) == 5)
finishedEvent.Set();
});
}
// wait till work is done
finishedEvent.WaitOne();
}
An alternative would be to not use a BackgroundWorker at all.
You can update the UI in WPF using the MainWindow´s dispatcher.
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
// Update UI
});
or
System.Windows.Application.Current.MainWindow.Dispatcher.Invoke(() =>
{
// Update UI
});
Or in Windows Forms using a concrete Form
System.Windows.Forms.Form form = ...;
form.Invoke(new Action( () =>
{
//Update UI
}));

Cross-thread InvalidOperationException while trying to access SerialPort dynamically

I have multiple serial devices connected to my PC and I am working on a program that allows users select as many as ports they want and then the program will dynamically creates TabPage and adds them to TabControl.
Each tab page will also have a multiline TextBox that will show the incoming data from the assigned serialport to it.
Here is my code that tries to create these controls dynamically:
private void AddSerialPort(string portName)
{
ActiveSerialPorts.Add(portName);
if (!tabControlActiveSerialPorts.Enabled)
tabControlActiveSerialPorts.Enabled = true;
var page = new TabPage(portName);
page.Text = portName;
var tb = new TextBox();
tb.Name = portName;
tb.Dock = DockStyle.Fill;
tb.BackColor = Color.Black;
tb.Multiline = true;
page.Controls.Add(tb);
tabControlActiveSerialPorts.TabPages.Add(page);
var sp = new SerialPort(portName, 115200, Parity.None, 8, StopBits.One);
sp.Open();
tb.Tag = sp;
sp.DataReceived += delegate
{
tb.Text += sp.ReadExisting(); //LINE 87
};
}
PROBLEM:
Here is the error I get on runtime, and break lands on line 87 (commented on code above):
Cross-thread operation not valid: Control 'COM16' accessed from a thread other than the thread it was created on.
What could be the possible pitfall here?
You're receiving data on background thread and trying to update the UI from the non-UI thread. You need to marshal the data from the background thread to the UI thread in order to update the control. This can be done using the Control.Invoke method.
sp.DataReceived += delegate
{
if (tb.InvokeRequired)
{
tb.Invoke(new Action(() =>
{
tb.Text += sp.ReadExisting();
}));
}
else
{
tb.Text += sp.ReadExisting();
}
}

Updating UI with BackgroundWorker in WPF

I am currently writing a simple WPF 3.5 application that utilizes the SharePoint COM to make calls to SharePoint sites and generate Group and User information. Since this process takes awhile I want to show a ProgressBar while the groups are being generated. The desired process is as follows:
User enters url and clicks button to fetch site data.
ProgressBar begins animation
Groups are generated and names are added to a ListView
Upon completion ProgressBar animation ends
The problem I am running into is that the UI is never updated. Neither the ProgressBar or the ListView makes any changes. If anyone has any ideas to help with the code below it would be greatly appreciated.
private void GetGroupsAndUsersButton_Click(object sender, RoutedEventArgs e)
{
siteUrl = "";
if (SiteURLTextBox.Text.Length > 0)
{
FetchDataProgressBar.IsIndeterminate = true;
mWorker = new BackgroundWorker();
mWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
mWorker.WorkerSupportsCancellation = true;
mWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync();
}
else
{
System.Windows.MessageBox.Show("Please enter a URL for the SharePoint site you wish to retrieve data");
}
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
siteUrl = SiteURLTextBox.Text;
GroupListView.ItemsSource = null;
try
{
using (SPSite site = new SPSite(siteUrl))
{
SPWeb web = site.OpenWeb();
SPGroupCollection collGroups = web.SiteGroups;
if (GroupNames == null)
GroupNames = new List<string>();
foreach (SPGroup oGroup in collGroups)
{
GroupListView.Items.Add(new ListViewItem() { Content = oGroup.Name });
}
foreach (ListViewItem item in GroupListView.Items)
{
item.MouseLeftButtonUp += item_MouseLeftButtonUp;
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl);
}
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
FetchDataProgressBar.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
FetchDataProgressBar.IsIndeterminate = false;
}
));
}
At first you need to support ProgressChanged events.
Update your BackgroundWorker initialization to:
GroupListView.ItemSource = null;
mWorker = new BackgroundWorker();
mWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
mWorker.WorkerSupportsCancellation = true;
mWorker.WorkerReportsProgress = true;
mWorker.ProgressChanged += OnProgressChanged;
mWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync(SiteURLTextBox.Text);
After that you have to add a OnProgressChanged handler:
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
FetchDataProgressBar.Value = e.ProgressPercentage;
ListViewItem toAdd = (ListViewItem)e.UserState;
toAdd.MouseLeftButtonUp += item_MouseLeftButtonUp;
GroupListView.Items.Add(toAdd);
}
Therefore you have to change your DoWork:
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
try
{
using (SPSite site = new SPSite((String)e.Argument))
{
SPWeb web = site.OpenWeb();
SPGroupCollection collGroups = web.SiteGroups;
if(GroupNames == null)
GroupNames = new List<string>();
int added = 0;
foreach(SPGroup oGroup in collGroups)
{
added++;
ListViewItem tmp = new ListViewItem() {
Content = oGroup.Name
};
worker.ReportProgress((added * 100)/collGroups.Count,tmp);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl);
}
}
That's because you're not allowed to change GUI on DoWork.
After that, each ListViewItem is added separately to your ListView. I would also recommend, that your URL is passed as an argument to RunWorkerAsync.
Edit: Add percentage to OnProgressChanged.
In your DoWork method, you are manipulating WPF controls in code on a background thread, which you are not supposed to do. Actually, you should receive errors like "Cannot access control from other thread". Probably those exceptions are caught by your catch-all error handler, and maybe even the MessageBox doesn't work from the background thread.
As a quick fix, you would have to make siteURL and collGroups class fields, move everything before the using block to your GetGroupsAndUsersButton_Click method, and everything starting with the first foreach loop to the RunworkerCompleted event, so that all code which accesses controls runs on the UI thread.
Another thing you should change is that you should not create ListViewItems in code, but use a DataTemplate instead... this is not connected to your problem, though.
You'll need:
mWorker.WorkerReportsProgress = true;
mWorker.ProgressChanged +=
new ProgressChangedEventHandler(worker_ProgressChanged);
Then in your DoWork you'll need to call:
var worker = (BackgroundWorker)sender;
worker.ReportProgress(progressAmount);
Good worked example here: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

Multithreading in C# with Win.Forms control

I'm beginner in C#. And i have problem with threads when i using win.forms. My application freezes. What the problem with this code? I'm using microsoft example from msdn.
Here's my code:
delegate void SetTextCallback(object text);
private void WriteString(object text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(WriteString);
this.Invoke(d, new object[] { text });
}
else
{
for (int i = 0; i <= 1000; i++)
{
this.textBox1.Text = text.ToString();
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread th_1 = new Thread(WriteString);
Thread th_2 = new Thread(WriteString);
Thread th_3 = new Thread(WriteString);
Thread th_4 = new Thread(WriteString);
th_1.Priority = ThreadPriority.Highest; // самый высокий
th_2.Priority = ThreadPriority.BelowNormal; // выше среднего
th_3.Priority = ThreadPriority.Normal; // средний
th_4.Priority = ThreadPriority.Lowest; // низкий
th_1.Start("1");
th_2.Start("2");
th_3.Start("3");
th_4.Start("4");
th_1.Join();
th_2.Join();
th_3.Join();
th_4.Join();
}
There is a deadlock - UI thread is waiting for threads to complete with Thread.Join() while the worker threads are trying to send a message to UI using blocking Control.Invoke(). Replacing the Invoke in the thread code by BeginInvoke() will make the deadlock go away
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(WriteString);
// BeginInvoke posts message to UI thread asyncronously
this.BeginInvoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text.ToString();
}
It freezes because of the Join calls. Thread.Join() makes the current thread wait after another one is complete.

Categories

Resources