I am trying to write an application the is constantly searching for host on a lan. When I run this as a console as the countdown.Wait() seems to work fine. However when I bring the code into a windows form the countdown.Signal() does not seem to decrement its counter. Not sure what the problem is.
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;
using System.Net.NetworkInformation;
using System.Diagnostics;
using System.Net;
using System.Threading;
namespace Multi_Threaded
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ps.run_ping_probe();
}
void ps_ProbeCompleted(object sender, PingProbeCompletedArguments e)
{
MessageBox.Show("I found " + e.ip_adresses_list_of_host.Count.ToString() + "host(s)");
}
}
public delegate void PingProbeCompleted(object sender,PingProbeCompletedArguments e);
public class PingProbeCompletedArguments : EventArgs
{
public List<string> ip_adresses_list_of_host;
}
public class PortScanner
{
public event PingProbeCompleted ProbeCompleted;
static List<string> ip_adresses = new List<string>();
static CountdownEvent countdown;
public void run_ping_probe()
{
ip_adresses.Clear();
countdown = new CountdownEvent(1);
string ipBase = "10.125.";
for (int sub = 0; sub < 14; sub++)
{
for (int i = 1; i < 255; i++)
{
string ip = ipBase + sub.ToString() + "." + i.ToString();
Ping p = new Ping();
p.PingCompleted += new PingCompletedEventHandler(p_PingCompleted);
countdown.AddCount();
p.SendAsync(ip, 100, ip);
}
}
countdown.Signal();
countdown.Wait();
PingProbeCompletedArguments e = new PingProbeCompletedArguments();
e.ip_adresses_list_of_host = ip_adresses;
ProbeCompleted(this, e);
}
private void p_PingCompleted(object sender, PingCompletedEventArgs e)
{
string ip = (string)e.UserState;
if (e.Reply.Status == IPStatus.Success)
{
ip_adresses.Add(ip + "\t" + e.Reply.RoundtripTime + " ms");
}
countdown.Signal();
}
}
Yes, your code deadlocks when you use it in a Winforms project. The problem is that the Ping class makes a best effort to raise the PingCompleted event on the same thread that called SendAsync(). It uses the AsyncOperationManager.CreateOperation() method to do so.
Problem is, that actually works in a Winforms app. It tries to raise the event on the main thread. But that cannot work since you blocked the main thread with the countdown.Wait() call. The ping cannot complete since the main thread is blocked. The main thread cannot complete since the ping doesn't complete. Deadlock city.
It works in a Console mode app since it doesn't have a synchronization provider like Winforms does. The PingComplete event will be raised on a threadpool thread.
Blocking the UI thread is fundamentally flawed. The quick fix is to run the code on a worker thread. Beware that this makes the ProbeCompleted event fired on that worker as well. Use Control.BeginInvoke() to marshal it to the UI thread. Or use BackgroundWorker.
private void Form1_Load(object sender, EventArgs e) {
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ThreadPool.QueueUserWorkItem((w) => ps.run_ping_probe());
}
And don't forget to remove the extra Signal() call.
your wait handler is run under a thread from the threadpool.
you need to get back into the UI thread to have the UI updated (due to the message loop that the UI runs on) - for that you use SynchronizationContext
Here more info on how to you it:
http://www.codeproject.com/KB/threads/SynchronizationContext.aspx
Related
This question already has answers here:
How do I update the GUI from another thread?
(47 answers)
Closed 5 years ago.
I recently started learning C#, I am trying to repeat a method every minute with help of a timer. The method changes the value of the label. However, I get the following error:
$exception {"Cross-thread operation not valid: Control 'label1'
accessed from a thread other than the thread it was created
on."} System.Exception {System.InvalidOperationException}
I have tried searching for a solution and each thread confuses me. I don't just need the correct code but also explanation as I want to learn manipulating UI with help of Timers and Threading.
The following is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Timers;
using System.Windows.Forms;
namespace Winforms_Timer_and_Thread
{
public partial class Form1 : Form
{
System.Timers.Timer myTimer = new System.Timers.Timer();
public Form1()
{
myTimer.Enabled = true;
InitializeComponent();
}
public void startMethod(object senter, ElapsedEventArgs e)
{
int cntr = 0;
cntr++;
label1.Text = "Executed: " + cntr.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Started!";
myTimer.Enabled = true;
myTimer.Interval=(1*60*1000);
myTimer.Elapsed += new System.Timers.ElapsedEventHandler(startMethod);
}
}
}
If you change to use a System.Windows.Forms.Timer, you won't have that problem, since it's executed in the same UI thread.
You only have to change:
The type of the timer from System.Timers.Timer to System.Windows.Forms.Timer
The event that gets subscribed to from Elapsed to Tick
The event signature, where ElapsedEventArgs e becomes EventArgs e
You also only need to subscribe to the event once, not each time the timer is enabled, so move that to the Form_Load event instead, along with the Interval assignment (although that can be changed any time).
You also might want to store the counter variable outside of the StartMethod, so it increments each time it's executed, and then reset it to zero each time you start the timer:
public partial class Form1 : Form
{
readonly System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
private int tickCounter;
public Form1()
{
InitializeComponent();
myTimer.Interval = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
myTimer.Tick += StartMethod;
}
private void StartMethod(object sender, EventArgs e)
{
tickCounter++;
label1.Text = "Number of executions: " + tickCounter;
}
private void button1_Click(object sender, EventArgs e)
{
tickCounter = 0;
label1.Text = "Started!";
myTimer.Enabled = true;
}
}
I'm using GTK# to build a GUI. Some data is processed in the background and I'd like to see some info about the progress on the user interface. Here is some code demonstrating the way I am trying to do this:
using System;
using Gtk;
using System.Threading.Tasks;
using System.Threading;
public partial class MainWindow: Gtk.Window
{
//a button and a textfield
private VBox VB = new VBox();
private Button B = new Button("Push dis");
private Label L = new Label("0");
public MainWindow () : base (Gtk.WindowType.Toplevel)
{
B.Clicked += OnClickEvent;
////////////////////
VB.PackStart (B);
VB.PackStart (L);
Add (VB);
ShowAll ();
Build ();
}
protected void OnDeleteEvent (object sender, DeleteEventArgs a)
{
Application.Quit ();
a.RetVal = true;
}
//async method incrementing variable, simulating some work and sending its progress
protected async Task CounterGUIUpdateAsync(IProgress<string> progress)
{
await Task.Run (() => {
for (int i = 0; i <= 10000; i++) {
Thread.Sleep (100);
if(progress != null)
{
var stri = Convert.ToString(i);
progress.Report(stri);
}
}
});
}
//event handler for the button
protected async void OnClickEvent(object sender, EventArgs e)
{
var ProgressIndicator = new Progress<string> (ReportProgress);
await CounterGUIUpdateAsync (ProgressIndicator);
}
//action connected to the progress instance
protected void ReportProgress(string value)
{
L.Text = value;
}
}
Running the code will start off as expected, but at some point it is likely that the displayed counter gets stuck. The GUI won't update anymore, turning black if it has been minimized. It is still functional though.
Help is appreciated very much.
I think your problem is that you're not using the main thread (the gui thread) when using the Gtk+ API. You need to use Gtk.Application.Invoke() passing a delegate that manipulates the UI, so the action is performed in the correct thread.
You can read more about this here.
I am trying to write an application the is constantly searching for host on a lan. When I run this as a console as the countdown.Wait() seems to work fine. However when I bring the code into a windows form the countdown.Signal() does not seem to decrement its counter. Not sure what the problem is.
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;
using System.Net.NetworkInformation;
using System.Diagnostics;
using System.Net;
using System.Threading;
namespace Multi_Threaded
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ps.run_ping_probe();
}
void ps_ProbeCompleted(object sender, PingProbeCompletedArguments e)
{
MessageBox.Show("I found " + e.ip_adresses_list_of_host.Count.ToString() + "host(s)");
}
}
public delegate void PingProbeCompleted(object sender,PingProbeCompletedArguments e);
public class PingProbeCompletedArguments : EventArgs
{
public List<string> ip_adresses_list_of_host;
}
public class PortScanner
{
public event PingProbeCompleted ProbeCompleted;
static List<string> ip_adresses = new List<string>();
static CountdownEvent countdown;
public void run_ping_probe()
{
ip_adresses.Clear();
countdown = new CountdownEvent(1);
string ipBase = "10.125.";
for (int sub = 0; sub < 14; sub++)
{
for (int i = 1; i < 255; i++)
{
string ip = ipBase + sub.ToString() + "." + i.ToString();
Ping p = new Ping();
p.PingCompleted += new PingCompletedEventHandler(p_PingCompleted);
countdown.AddCount();
p.SendAsync(ip, 100, ip);
}
}
countdown.Signal();
countdown.Wait();
PingProbeCompletedArguments e = new PingProbeCompletedArguments();
e.ip_adresses_list_of_host = ip_adresses;
ProbeCompleted(this, e);
}
private void p_PingCompleted(object sender, PingCompletedEventArgs e)
{
string ip = (string)e.UserState;
if (e.Reply.Status == IPStatus.Success)
{
ip_adresses.Add(ip + "\t" + e.Reply.RoundtripTime + " ms");
}
countdown.Signal();
}
}
Yes, your code deadlocks when you use it in a Winforms project. The problem is that the Ping class makes a best effort to raise the PingCompleted event on the same thread that called SendAsync(). It uses the AsyncOperationManager.CreateOperation() method to do so.
Problem is, that actually works in a Winforms app. It tries to raise the event on the main thread. But that cannot work since you blocked the main thread with the countdown.Wait() call. The ping cannot complete since the main thread is blocked. The main thread cannot complete since the ping doesn't complete. Deadlock city.
It works in a Console mode app since it doesn't have a synchronization provider like Winforms does. The PingComplete event will be raised on a threadpool thread.
Blocking the UI thread is fundamentally flawed. The quick fix is to run the code on a worker thread. Beware that this makes the ProbeCompleted event fired on that worker as well. Use Control.BeginInvoke() to marshal it to the UI thread. Or use BackgroundWorker.
private void Form1_Load(object sender, EventArgs e) {
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ThreadPool.QueueUserWorkItem((w) => ps.run_ping_probe());
}
And don't forget to remove the extra Signal() call.
your wait handler is run under a thread from the threadpool.
you need to get back into the UI thread to have the UI updated (due to the message loop that the UI runs on) - for that you use SynchronizationContext
Here more info on how to you it:
http://www.codeproject.com/KB/threads/SynchronizationContext.aspx
In the designer i put backgroundworker and i have two events: Do Work and Progress Changed.
I used breakpoint and its getting inside the Do Work event but it never get into the Progress Changed event. Its never stop there like the event isnt working. Why the progrss changed event isnt working ?
This is the code:
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;
using Google.GData.Client;
using Google.GData.Extensions;
using Google.GData.Extensions.MediaRss;
using Google.GData.YouTube;
using Google.YouTube;
using System.Threading;
namespace YoutubeTesting
{
public partial class Form1 : Form
{
YouTubeRequestSettings settings;
YouTubeRequest request;
string devkey = "AI39si6xhSQXx95FTYIACWPfq-lLIphblgaReuz9z6VEjR1Q6YjrV6FRN2U6FN6P6-lGF2OYaUZhCVOKJ_MCk4o6kPeUszvf5A";
string username = "chocolade13091972#gmail.com";
string password = "password";
public Form1()
{
InitializeComponent();
worker.RunWorkerAsync();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void upload()
{
try
{
settings = new YouTubeRequestSettings("You Manager", devkey, username, password);
settings.Timeout = -1;
request = new YouTubeRequest(settings);
Video video = new Video();
video.Title = "test";
video.Tags.Add(new MediaCategory("Comedy", YouTubeNameTable.CategorySchema));
video.Keywords = "Comedy";
video.Private = false;
video.MediaSource = new MediaFileSource("d:\\VIDEO0037.3gp", "video/3gp");
request.Upload(video);
MessageBox.Show("Successfully Uploaded");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
upload();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox1.Text = e.ProgressPercentage.ToString();
}
}
}
You need to report the progress using worker.ReportProgress()
From MSDN:
If you need the background operation to report on its progress, you
can call the ReportProgress method to raise the ProgressChanged event.
The WorkerReportsProgress property value must be true, or
ReportProgress will throw an InvalidOperationException.
It is up to you to implement a meaningful way of measuring your
background operation's progress as a percentage of the total task
completed.
The call to the ReportProgress method is asynchronous and returns
immediately. The ProgressChanged event handler executes on the thread
that created the BackgroundWorker.
You have to set this.
backgroundWorker.WorkerReportsProgress = true;
Gets or sets a value indicating whether the BackgroundWorker can
report progress updates.
EDIT
If still not working checks whether you have bind the event properly in the designer code. Or just add something like below in your class.
backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.worker_ProgressChanged);
In your Upload method you have to report progress. Otherwise above event won't fire. Keep in mind that, it's not easy to report actual progress always.
Below is an example code for a DoWork method. Look at here if you want to see a complete example.
static void bw_DoWork (object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i += 20)
{
if (_bw.CancellationPending) { e.Cancel = true; return; }
_bw.ReportProgress (i);
Thread.Sleep (1000); // Just for the demo... don't go sleeping
} // for real in pooled threads!
e.Result = 123; // This gets passed to RunWorkerCompleted
}
i am in the process implementing SQLdepenency i would like to know in case of Dependency Handler exeuctues will it spun a different thred from main Process ? What will happen when the event handler triggers? Do i need to worry about any multithreds issues?
public void CreateSqlDependency()
{
try
{
using (SqlConnection connection = (SqlConnection)DBFactory.GetDBFactoryConnection(Constants.SQL_PROVIDER_NAME))
{
SqlCommand command = (SqlCommand)DBFactory.GetCommand(Constants.SQL_PROVIDER_NAME);
command.CommandText = watchQuery;
command.CommandType = CommandType.Text;
SqlDependency dependency = new SqlDependency(command);
//Create the callback object
dependency.OnChange += new OnChangeEventHandler(this.QueueChangeNotificationHandler);
SqlDependency.Start(connectionString);
DataTable dataTable = DBFactory.ExecuteSPReDT(command);
}
}
catch (SqlException sqlExp)
{
throw sqlExp;
}
catch (Exception ex)
{
throw ex;
}
}
public void QueueChangeNotificationHandler(object caller, SqlNotificationEventArgs e)
{
if(e.Info == SqlNotificationInfo.Insert)
Fire();
}
It works in a separate thread, but there is only one such thread for all notifications. Have a look at the SQLDependency section in this article
SqlDependency documentation on MSDN mention about possibility that the OnChange event may be generated on a different thread from the thread that initiated command execution.
You should read Detecting Changes with SqlDependency (ADO.NET) article from MSDN which explain similar scenario.
It will spawn a new worker thread to wait for the dependency notifications--but that's what you want (otherwise your main program loop would be held up waiting for something that may never happen!).
The example code at this page shows to how avoid issues where the worker thread that gets the dependency/query notifications doesn't have the right to update the UI; their method passes the task to the UI thread so it will succeed (see step #12).
It does spawn in a different thread!! You can create a simple Windows Application to test this, and you'll see how the OnChange handler can't modify any UI controls directly (you'd get something like "Cross-thread operation not valid: Control XXX accessed from a thread other than the thread it was created on"). To overcome this, you call BeginInvoke.
Here's a simple Windows application to test SqlDependencies (I hope you can imagine the UI).
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Linq;
using System.Data.Linq.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;
namespace TestSqlDependancies
{
public partial class SqlDependencyTester : Form
{
SqlDependency sqlDepenency = null;
SqlCommand command;
SqlNotificationEventArgs msg;
public Form1()
{
InitializeComponent();
}
private void label1_Click(object sender, EventArgs e)
{
}
public delegate void InvokeDelegate();
void sqlDepenency_OnChange(object sender, SqlNotificationEventArgs e)
{
msg = e;
this.BeginInvoke(new InvokeDelegate(Notify));
}
private void Notify()
{
listBox1.Items.Add(DateTime.Now.ToString("HH:mm:ss:fff") + " - Notfication received. SqlDependency " + (sqlDepenency.HasChanges ? " has changes." : " reports no change. ") + msg.Type.ToString() + "-" + msg.Info.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
SetDependency();
}
private void SetDependency()
{
try
{
using (TicketDataContext dc = new TicketDataContext())
{
SqlDependency.Start(dc.Connection.ConnectionString);
command = new SqlCommand(textBox1.Text, (SqlConnection)dc.Connection);
sqlDepenency = new SqlDependency(command);
sqlDepenency.OnChange += new OnChangeEventHandler(sqlDepenency_OnChange);
command.Connection.Open();
command.ExecuteReader();
}
}
catch (Exception e)
{
listBox1.Items.Add(DateTime.Now.ToString("HH:mm:ss:fff") + e.Message);
}
}
}
}