This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to update GUI from another thread in C#?
I've currently got a C# program to run a query and display the results in a datagridview.
The query due to size of records takes a while (20-30 seconds) to run.
I thought I would add an animation so the user at least knows the software is running and has not stopped working.
Of course I can't run anything when the call is being made to the procedure so I looked into threading.
Here is my code (forgive me, I haven't really put in comments yet):
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.Data.Sql;
using System.Data.SqlClient;
using System.Threading;
namespace RepSalesNetAnalysis
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
pictureBox2.Visible = false;
}
private void button1_Click(object sender, EventArgs e)
{
GetsalesFigures();
}
private void Form1_Load(object sender, EventArgs e)
{
AutofillAccounts();
}
private void GetsalesFigures()
{
try
{
string myConn = "Server=herp;" +
"Database=shaftdata;" +
"uid=fake;" +
"pwd=faker;" +
"Connect Timeout=120;";
string acct;// test using 1560
SqlConnection conn = new SqlConnection(myConn);
SqlCommand Pareto = new SqlCommand();
BindingSource bindme = new BindingSource();
SqlDataAdapter adapt1 = new SqlDataAdapter(Pareto);
DataSet dataSet1 = new DataSet();
DataTable table1 = new DataTable();
Thread aniSql = new Thread(new ThreadStart(animateIcon));//CREATE THE THREAD
acct = accCollection.Text;
string fromDate = this.dateTimePicker1.Value.ToString("MM/dd/yyyy");
string tooDate = this.dateTimePicker2.Value.ToString("MM/dd/yyyy");
Pareto.Connection = conn;
Pareto.CommandType = CommandType.StoredProcedure;
Pareto.CommandText = "dbo.GetSalesParetotemp";
Pareto.CommandTimeout = 120;
Pareto.Parameters.AddWithValue("#acct", acct);
Pareto.Parameters.AddWithValue("#from", fromDate);
Pareto.Parameters.AddWithValue("#too", tooDate);
aniSql.Start(); //START THE THREAD!
adapt1.Fill(dataSet1, "Pareto");
aniSql.Abort(); //KILL THE THREAD!
//pictureBox2.Visible = false;
this.dataGridView1.AutoGenerateColumns = true;
this.dataGridView1.DataSource = dataSet1;
this.dataGridView1.DataMember = "Pareto";
dataGridView1.AutoResizeColumns(
DataGridViewAutoSizeColumnsMode.AllCells);
}
catch (Exception execc)
{
MessageBox.Show("Whoops! Seems we couldnt connect to the server!"
+ " information:\n\n" + execc.Message + execc.StackTrace,
"Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
private void AutofillAccounts()
{
//get customers list and fill combo box on form load.
try
{
string myConn1 = "Server=derp;" +
"Database=AutoPart;" +
"uid=fake;" +
"pwd=faker;" +
"Connect Timeout=6000;";
SqlConnection conn1 = new SqlConnection(myConn1);
conn1.Open();
SqlCommand accountFill = new SqlCommand("SELECT keycode FROM dbo.Customer", conn1);
SqlDataReader readacc = accountFill.ExecuteReader();
while (readacc.Read())
{
this.accCollection.Items.Add(readacc.GetString(0).ToString());
}
conn1.Close();
}
catch(Exception exc1)
{
MessageBox.Show("Whoops! Seems we couldnt connect to the server!"
+ " information:\n\n" + exc1.Message + exc1.StackTrace,
"Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
public void animateIcon()
{
// animate
pictureBox2.Visible = true;
}
}
}
As you can see I want to run the animation just before the procedure call and then end it just after.
My knowledge on threads is brand new. I've looked around but i'm getting a little confused at the moment.
Here's my error:
Thrown: "Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on." (System.InvalidOperationException) Exception Message = "Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.", Exception Type = "System.InvalidOperationException"
I need a very simple way of performing an animation while my sql proc is reading.
Something like picture.visible = true when its started and false when it ends.
Invoke is needed if you want to do this.
private delegate void InvokeDelegate();
public void DoSomething()
{
if (InvokeRequired)
{
Invoke(new InvokeDelegate(DoSomething));
return;
}
// dosomething
}
you can also add variables to the delegate and use them:
private delegate void InvokeDelegate(string text);
public void DoSomething(string text)
{
if (InvokeRequired)
{
Invoke(new InvokeDelegate(DoSomething), text);
return;
}
// dosomething with text
}
hope this helps :).
stefan
As others have pointed out, you cannot perform UI-related operations on a separate thread.
If you want your application to be responsive, you should perform the data operation on a separate thread instead.
If you just want to show the PictureBox control, you don't need the extra thread at all:
pictureBox2.Visible = true;
pictureBox2.Refresh(); // <-- causes the control to be drawn immediately
...large operation...
pictureBox2.Visible = false;
However if the user for instance alt-tabs back and forth, or drags another window over yours, the application would seem to hang as the UI thread is busy performing the data operation.
I'm suprised that so many people advise you to keep your current code and use InvokeRequired and Invoke, even though Invoke will only execute when the UI thread has time to process it (AFTER the data operation).
You need to use InvokeRequired to access/modify a control from a thread other than the main thread of your form. Documentation here: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx
Have you tried with Tasks???
I make a simple test to show how I would make something similar (in WPF):
The XAML:
<Window x:Class="TaskLoading.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="90,33,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<Image Height="118" HorizontalAlignment="Left" Margin="90,80,0,0" Name="imgLoading" Stretch="Fill" VerticalAlignment="Top" Width="122" Visibility="Hidden" Source="/TaskLoading;component/loader_big.gif" />
</Grid>
</Window>
The code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Threading.Tasks;
namespace TaskLoading
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void bigProcess(){
Thread.Sleep(5000);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
imgLoading.Visibility = Visibility.Visible; //Make the icon visible.
/* Start the bigProcess in a background thread: */
Task changeIcon = Task.Factory.StartNew(() =>
{
bigProcess();
});
/* At the end of the process make invisible the icon */
changeIcon.ContinueWith((r) =>
{
imgLoading.Visibility = Visibility.Hidden;
},
TaskScheduler.FromCurrentSynchronizationContext()
);
}
}
}
public void animateIcon()
{
Action action=()=>pictureBox2.Visible = true;
// animate
this.Invoke(action);
}
Related
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
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 have already implemented a functionnal application that parses 26 pages of html all at once to produce an xml file with data contained on the web pages. I would need to implement a thread so that this method can work in the background without causing my app to seems unresponsive.
Secondly, I have another function that is decoupled from the first one which compares two xml files to produce a third one and then transform this third xml file to produce an html page using XSLT. This would have to be on a thread, where I can click Cancel to stop the thread whithout crashing the app.
What is the easiest best way to do this using WPF forms in VS 2010 ?
I have chosen to use the BackgroundWorker.
BackgroundWorker implementation:
public partial class MainWindow : Window
{
private BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.LoadFiles();
}
private void btnCompare_Click(object sender, EventArgs e)
{
if (bw.IsBusy != true)
{
progressBar2.IsIndeterminate = true;
// Start the asynchronous operation.
bw.RunWorkerAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
StatsProcessor proc = new StatsProcessor();
if (lstStatsBox1.SelectedItem != null)
if (lstStatsBox2.SelectedItem != null)
proc.CompareStats(lstStatsBox1.SelectedItem.ToString(), lstStatsBox2.SelectedItem.ToString());
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar2.IsIndeterminate = false;
progressBar2.Value = 100;
}
I have started with the bgworker solution, but it seems that the bw_DoWork method is never called when btnCompare is clicked, I must be doing something wrong... I am new to threads.
If you're new to threading I think the easiest to start with would be using a BackgroundWorker:
It uses another thread: You can asynchronously perform the HTML parsing while your app remains responsive
It supports cancellation: You can can cancel the XML to HTML conversion
The BackgroundWorker is event-driven so it's much easier to get your ahead around if you're new to multi-threading. The .NET 4 Task library is much more flexible, but a little more involved to take advantage of especially with UI updates.
Example:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler((o, args) =>
{
//Long running stuff here
Thread.Sleep(10000);
string result = "Hi UI!";
args.Result = result;
});
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((o, args) =>
{
if (args.Result != null)
{
SomeTextBox.Text = args.Result;
}
});
worker.RunWorkerAsync();
Since you're using .NET 4 and VS2010, I would use the Task Parallel Library. Here's an example that shows the basics of how to background a task and marshal UI updates back to the GUI. I think you need to use a BlockingCollection to implement the second part.
There is a good blog post that talks about chaining tasks and supporting cancellation:
http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx
Here's the example of back-grounding without a background worker
MainWindow.xaml - it's just a window with a status label. The min necessary to get the point across:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="378" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="94" SizeToContent="Height">
<Grid>
<StackPanel>
<Button Click="Button_Click">Press Me</Button>
<StatusBar>
<Label Name="TheLabel" Content="Status" />
</StatusBar>
</StackPanel>
</Grid>
</Window>
Here's the Code Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading.Tasks;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// Dispatchers are an easy way to update the UI from a background thread
private System.Windows.Threading.Dispatcher Dispatcher;
// the Current Task - think of this as your background worker
private Task CurrentTask;
public MainWindow()
{
InitializeComponent();
// this line needs to run on the main thread.
Dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
}
private void UpdateStatus(string msg)
{
// marshall the call back to the main thread
Dispatcher.Invoke( new Action( () => TheLabel.Content = msg ));
}
// we're going to calculate the average of a sh*tload (technical term) of numbers
private void DoSomeWork()
{
// update the UI
UpdateStatus("Doing Work");
Random r = new Random();
int max = r.Next(100000000, 1000000000);
double avg = Enumerable.Range(0, max).Average();
// update the UI
UpdateStatus(string.Format("Done - Average = {0}", avg));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// make sure the current task isn't already running
if (CurrentTask != null && CurrentTask.IsCompleted == false)
{
MessageBox.Show("Wait");
return;
}
// start a new one
CurrentTask = Task.Factory.StartNew(() =>
{
DoSomeWork();
});
}
}
}
You can easily do multithreading using Threadpools. However, the trouble and issues you will have everything to do with your code that produces this XML file. If you restrict this code to only be running one thread at a time - but your UI still responsive, you will have less issues with your code. (Instead of trying to run a thread and process multiple web pages at one time)
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);
}
}
}
}