SqlDependency using BackgroundWorker - c#

I have a table in a SQL Server database that represents a log file of some actions inserted from a running windows service. Everything is working well.
But, I have a Windows application that gets the latest rows that have been inserted in the log table and views it in a DataGridView. While developing this application I depended on Using SqlDependency in a Windows Application from MSDN. It is working well, but when the log table receives a large number of log details, the Windows app hangs up and the main thread pool becomes too busy.
I want to run the same code referenced in the previous link in a separated thread pool by using Thread class or BackgroundWorker control. This means a thread for using UI controls and another one for listening to the database changes and get it into the DataGridView.
You can see the UI screenshot from this link "UI"
No. (1): This GroupBox represents the UI tools which users can use it while monitoring.
No. (2): The Start button is responsible for beginning to listen and receive updates from the database and refill the DataGridView.
No. (3): This grid represents the new logs that have been inserted in the database.
No. (4): This number (38 changes) represents the count of listening of sql dependency to the database changes.
My code:
public partial class frmMain : Form
{
SqlConnection conn;
const string tableName = "OutgoingLog";
const string statusMessage = "{0} changes have occurred.";
int changeCount = 0;
private static DataSet dataToWatch = null;
private static SqlConnection connection = null;
private static SqlCommand command = null;
public frmMain()
{
InitializeComponent();
}
private bool CanRequestNotifications()
{
// In order to use the callback feature of the
// SqlDependency, the application must have
// the SqlClientPermission permission.
try
{
SqlClientPermission perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// This event will occur on a thread pool thread.
// Updating the UI from a worker thread is not permitted.
// The following code checks to see if it is safe to
// update the UI.
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
// If InvokeRequired returns True, the code
// is executing on a worker thread.
if (i.InvokeRequired)
{
// Create a delegate to perform the thread switch.
OnChangeEventHandler tempDelegate = new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
// Marshal the data from the worker thread
// to the UI thread.
i.BeginInvoke(tempDelegate, args);
return;
}
// Remove the handler, since it is only good
// for a single notification.
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
// At this point, the code is executing on the
// UI thread, so it is safe to update the UI.
++changeCount;
lblChanges.Text = String.Format(statusMessage, changeCount);
this.Refresh();
// Reload the dataset that is bound to the grid.
GetData();
}
private void GetData()
{
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
}
}
private void btnStart_Click(object sender, EventArgs e)
{
changeCount = 0;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Remove any existing dependency connection, then create a new one.
SqlDependency.Stop("<my connection string>");
SqlDependency.Start("<my connection string>");
if (connection == null)
{
connection = new SqlConnection("<my connection string>");
}
if (command == null)
{
command = new SqlCommand("select * from OutgoingLog", connection);
}
if (dataToWatch == null)
{
dataToWatch = new DataSet();
}
GetData();
}
private void frmMain_Load(object sender, EventArgs e)
{
btnStart.Enabled = CanRequestNotifications();
}
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
SqlDependency.Stop("<my connection string>");
}
}
What I want exactly: when user click the Start button, the application run the code in a separated thread pool.

First if I understand well the change notification is already executed on another thread so using one more threading layer should be useless.
Indeed what causes the application to hang is the update of the UI, on the UI thread.
Could you show the code responsible for this update please?
If there is a lot of notifications visual update will be longer and you can't do much:
update the grid by chunks to smooth the update: instead of inserting 1000 new records you run 10 updates of 100 records, but you take the risk of being overwhelmed by data if you don't process them fast enough
using a collection that handles notification natively like a BindingList could help
Moreover what you can do to enhance the user experience, avoiding the unpleasant "it hangs" effect, is displaying a progress bar or a simple spinner.
UPDATE:
So if the first part of the GetData function is the bottleneck then indeed you could use another thread, e.g. from the thread-pool:
private void GetData()
{
// Start the retrieval of data on another thread to let the UI thread free
ThreadPool.QueueUserWorkItem(o =>
{
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
// Update the UI
dgv.Invoke(() =>
{
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
});
}
});
}
So the only part that will run on the UI thread is the update of the datagrid.
Not tested but hope this helps...
LAST? UPDATE:
With some synchronization to avoid concurrent execution:
AutoResetEvent running = new AutoResetEvent(true);
private void GetData()
{
// Start the retrieval of data on another thread to let the UI thread free
ThreadPool.QueueUserWorkItem(o =>
{
running.WaitOne();
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
running.Set();
// Update the UI
dgv.Invoke(() =>
{
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
});
}
});
}

Related

How to continuously query a database and update the UI using a BackgroundWorker

I have created a Table in SQL and it has only one Column, its value may change over time.
If my Column's value is greater than 10, I want to show a Label, if it is less than 10 the Label text should change its text.
I have tried this using BackgroundWorker, but it is not working as expected
The BackgroundWorker should never stop querying the database. If the value changes in the SQL table, then the value should change in the Label.
The code in the BackgroundWorker should always check the new value.
private bool sqlData()
{
try
{
SqlConnection con = new SqlConnection(
#"Server=NTN\TEST; Database=WinApp;User ID=sa; pwd=Rh#yt$33q");
SqlCommand cmd = new SqlCommand("select * from Timer_test", con);
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
if (rdr.HasRows)
{
while (rdr.Read())
{
value = (int)rdr["value"];
}
}
con.Close();
if (value > 10)
{
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (sqlData())
{
timer1.Start();
timer1.Enabled = true;
Thread.Sleep(0100);
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
inpo.Text = "value less than 10";
backgroundWorker1.RunWorkerAsync();
}
private void Home_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
The code posted here requires a complete rework, since - overall - it's not functional and also contains a number of issues (as not disposing of the Connection / Command), the use of a Timer in the DoWork event is uncalled for, as also is calling Thread.Sleep() in that context.
You can remove the BackGroundWorker and run a Task instead (using a Timer here is also feasible, but you need a threaded Timer to query your database, since it may cause stuttering if this takes a long-ish time, then you need to marshal the update to the UI Thread etc., well, read the notes here: Why does invoking an event in a timer callback cause following code to be ignored?).
Defines an IProgress<int> delegate that will update the UI when it's called from the ConstantUpdate() procedure that queries your database and also pre-define the query and connection strings, so you can pass them to the Task
The CancellationTokenSource is used to signal the procedure that queries the database (ConstantUpdate()) to terminate its operations, when the Cancel() method is called.
Note that you can also pass a TimeSpan object to a Constructor of the CancellationTokenSource, to automatically cancel it after a set period of time
IProgress<int> statusUpdate = null;
CancellationTokenSource cts = null;
string statusQuery = "SELECT [value] FROM Timer_test";
string statusCon = #"[Your Connection String]";
protected override void OnShown(EventArgs e) {
base.OnShown(e);
statusUpdate = new Progress<int>((value) => Updater(value));
cts = new CancellationTokenSource();
Task.Run(() => ConstantUpdate(statusUpdate, statusCon, statusQuery, 1500, cts.Token));
}
private void Updater(int value) =>
lblStatusUpdate.Text = value >= 10 ? $"Value is {value}" : "Value is less than 10";
The procedure that queries the database retrieves the value associated with the Column specified in the query, casts it to int (since this is what the IProgress<int> delegate is expecting) and calls the Report() method of the IProgress delegate, which in turn updates the UI
Task.Delay() is used to pause the loop for the specified milliseconds
The SqlConnection and SqlCommand are disposable objects, so let's declare these with using statements
Call the StopUpdate() method whenever needed (see below).
private async Task ConstantUpdate(IProgress<int> statusUpdate, string connection, string query, int intervalMS, CancellationToken stopToken = default)
{
try {
while (!stopToken.IsCancellationRequested) {
var delay = Task.Delay(intervalMS, stopToken);
using (var con = new SqlConnection(connection))
using (var cmd = new SqlCommand(query, con)) {
con.Open();
statusUpdate.Report((int)await cmd.ExecuteScalarAsync(stopToken));
}
await delay;
}
}
catch (TaskCanceledException) {
// Log it, if required, otherwise you can also remove the try/catch block
Debug.WriteLine("Update procedure cancelled");
}
}
private void StopUpdate() {
cts?.Cancel();
cts?.Dispose();
cts = null;
}
Now you can call the StopUpdate() method when needed. Of course, you need to call it when the Form closes, in case the task is still running. If it's already been stopped, nothing happens:
protected override void OnFormClosed(FormClosedEventArgs e) {
StopUpdate();
base.OnFormClosed(e);
}

How to do long polling in Outlook (VSTO) which triggers new Windows Form

I've created a VSTO for Outlook 2016 which does long polling to a SQL database which looks to see if there are messages available for the user (every 5 mins it checks). If a message is available it should display it to the user as the topmost window of the Outlook thread.
I've successfully done this with background workers and tasks, however both of these solutions display the new windows form (message) in their own threads which is NOT the top most window. Users can miss this message because it can be hidden behind outlook.
I need a way of accessing the Outlook Object Model from a Task or Background worker.
Background worker Example:
public Messenger()
{
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler((object doSender, DoWorkEventArgs doEvent) =>
{
try
{
SqlConnection messageDB = new SqlConnection(connectionString);
List<MessageItem> selectResults = new List<MessageItem>();
string querySelect = $"SELECT ......";
// Initialize Adapter
SqlDataAdapter trackerAdapter = new SqlDataAdapter(querySelect, messageDB);
// Initialize empty result dataset.
DataSet dbDataSet = new DataSet();
// Fill the results container.
trackerAdapter.Fill(dbDataSet);
// Get the data (table).
DataTable trackerTable = dbDataSet.Tables[0];
// Get the row data.
DataRow[] dataResults = trackerTable.Select();
foreach (DataRow item in dataResults)
{
MessageItem tempMessage = new MessageItem();
tempMessage.id = (int)item.ItemArray[0];
tempMessage.employeeID = (string)item.ItemArray[1];
tempMessage.messageEN = (string)item.ItemArray[2];
tempMessage.messageFR = (string)item.ItemArray[3];
tempMessage.action = (int)item.ItemArray[4];
selectResults.Add(tempMessage);
}
doEvent.Result = selectResults;
}
catch
{
// Problem connecting to database. Cancel the running task so it doesn't try again.
messageCancelTokenSource.Cancel();
}
});
var checkMessages = Task.Factory.StartNew(() =>
{
while (true)
{
// Checks if the task was cancelled.
if (messageCancelToken.IsCancellationRequested)
{
break;
}
// Execute the job.
backgroundWorker.RunWorkerAsync();
Thread.Sleep(300000);
}
}, messageCancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
List<MessageItem> results = e.Result as List<MessageItem>;
foreach (MessageItem message in messages)
{
MessageWindow myMessage = new MessageWindow ( message );
myMessage.ShowDialog();
}
}
I've also tried completely getting rid of the background worker and using only a task:
Task<Task> task = Task.Factory.StartNew(async () =>
{
while (true)
{
// SQL Commands
// Display message
await Task.Delay(TimeSpan.FromSeconds(10));
}
}, TaskCreationOptions.LongRunning);
It does not sound like you actually need to access the Outlook Object Model on a secondary thread. The most you actually need is to retrieve the HWND of the main Outlook window (e.g. by casting Application.ActiveExplorer to IOleWindow and calling IOleWindow.GetWindow) on the main thread and use that handle later to properly parent your custom form.
Dmitry certainly set me on the right track. However as I'm pretty new to C# and .NET I wasn't able to fill in all the blanks. After combining pieces from here and there below is the solution I found that works.
First, implement your own IOleWindow interface class. I've no idea why we have to do this but we do:
[ComImport]
[Guid("00000114-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleWindow
{
void GetWindow( out IntPtr phwnd );
void ContextSensitiveHelp( [In, MarshalAs ( UnmanagedType.Bool) ] bool fEnterMode );
}
If you need the IWin32Window object of the Outlook Explorer window immediately then in your ThisAddIn.cs do something like this
public NativeWindow OutlookWindow;
private void setOutlookWindow()
{
// Cast the Outlook Explorer window as your IOleWindow interface.
IOleWindow OutlookExplorerObj = Application.ActiveExplorer() as IOleWindow;
// This will store the Outlook Explorer window handle.
IntPtr OutlookExplorerHandle;
// Assign the window handle.
OutlookExplorerObj.GetWindow( out OutlookExplorerHandle);
// Create a blank IWin32Window implementation.
OutlookWindow = new NativeWindow();
// Assign the Outlook Explorer window handle to the IWin32Window object.
OutlookWindow.AssignHandle( OutlookExplorerHandle );
}
private void ThisAddIn_Startup( object sender, System.EventArgs e )
{
setOutlookWindow();
}
Then you can access the window object with Globals.ThisAddIn.OutlookWindow:
myWindow.ShowDialog( Globals.ThisAddIn.OutlookWindow );
This even works from Tasks or Background Workers.

How can i check db every 5 sec and not freezes the gui in C#?

I'm developing an application that needs to check the DB every 5 sec (with a query) and when it happens, the GUI freezes for about 0.5sec and that its really annoying.
This is my code, I'm using "System.Windows.Forms.Timer" but i can change it.
private void TimerBackground(Object myObject, EventArgs eventArgs)
{
// code to check from DataBase that takes about 0.5 sec and freezes the GUI
// Then it will display the result to a label in a form
}
void main(){
System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
myTimer.Tick += new EventHandler(TimerBackground);
myTimer.Interval = 5000;
myTimer.Start();
}
Is there a better way to do this?
EDITED:
Im using a simple query, like this:
string credentials = "Server=127.0.0.1;port=3306;Database=test;Uid=root;password=root;";
MySqlConnection conn = null;
try
{
conn = new MySqlConnection(credentials);
MySqlCommand command = conn.CreateCommand();
command.CommandText = "SELECT * FROM myTable;";
conn.Open();
MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
//...
}
}
catch { }
finally { if (conn != null)conn.Close(); }
[SOLVED]
Well, i was reading the comments and i decided to investigate about await which solved my problem.
I simply wrote this inside my "Timer Function" (TimerBackground):
private async void TimerBackground(Object myObject, EventArgs eventArgs)
{
// This prevents the GUI from freezing.
await Task.Run(() =>
{
runQueryFunction();
});
}
You can use Background Workers or Threads for that. Using Timer control will freeze the UI because Timer controls always run on the same thread from where they were called!
As I understand the question, the timer and its event are working fine, but the read operation from the database is causing a delay in the gui. One option is to use a thread for the read operation:
protected virtual void MyHandler(object sender, args e)
{
new Thread(() => {
myDatabaseObject.QueryForSomething();
}).Start();
}
This may not be the best threading method, but it communicates the idea.
Then, maybe add an event in the database object to fire if whatever you're looking for comes back true. I'd expect that this approach would not involve any noticeable delay in the gui.

sync data grid view

i have data grid view that its data source is a data table in form in c#,
how can i make it read table from database continuously
i mean if my program running in many computers in same network and connected with same database , if computer 1 add row to the database its appears automatically in computer 2 without clicking any button to refresh.
void load()
{
c.connect("sel_dep");
c.com.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da=new SqlDataAdapter (c.com);
DataTable dt = new DataTable();
c.open();
int last = 0;
while (true)
{
if (dt.Rows.Count > 0)
dt.Rows.Clear();
da.Fill(dt);
dd = dt;
if (dt.Rows.Count != last)
{
last = dt.Rows.Count;
this.Invoke((MethodInvoker)delegate { dataGridView1.DataSource = dt; dataGridView1.SelectedRows[0].Selected = true; label1.Text = dataGridView1.RowCount.ToString(); });
}
}
c.close();
}
private void Form3_Load(object sender, EventArgs e)
{
aa = new Thread(() => { load(); });
aa.Start();
}
this is my tray
If you are using Winforms to create the desktop application, you would not need timer.
Simply drag and drop Timer control from the Toolbox on your form, in design mode.
' The form load should be altered as below
private void Form3_Load(object sender, EventArgs e)
{
' Assume the Timer control is named as 'oTimer'
oTimer.Enabled = true;
oTimer.Interval = 60000; ' In Milliseconds, equates to 1 minute
oTimer.Start();
}
Create the Tick event for the Timer. This event would be fired each time the interval elapses.
private void oTimer_Tick(object sender, EventArgs e)
{
<Call your function to initiate/refresh the DataGrid.DataSource within this event>
}
In order to further understand how the Timer class works, refer Timer Class (System.Windows.Forms).
Also refer to Stackoverlow question Winforms Timer for Dummies with plenty of resources and tips to master the Timer control.
If you're using Sql Server you can have a look at Query Notifications, more specifically at SqlDependency class. It can be used to get notifications from sql server, when data changes, into your desktop application
Built upon the Service Broker infrastructure, query notifications
allow applications to be notified when data has changed. This feature
is particularly useful for applications that provide a cache of
information from a database, such as a Web application, and need to be
notified when the source data is changed.

Display loading message until complete SQL data retrieve

I'm using WeifenLuo dockpanel-suit.
I need to show some kind of splash form or loading message before SQL complets loading data.
What I've tried didn't work
public partial class frmPostventa : DockContent
{
private void frmPostventa_Load(object sender, EventArgs e)
{
bool done = false;
ThreadPool.QueueUserWorkItem((x) =>
{
using (var splashForm = new SplashForm())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
});
//Database task heavy load
cargarDatos();
done = true;
}
public void cargarDatos()
{
string strSQL = "exec SAI_ALGO.spPostventa";
SqlDataAdapter dataAdapter = new SqlDataAdapter(strSQL, strCon);
SqlCommandBuilder commandBuilder = new SqlCommandBuilder(dataAdapter);
// Populate a new data table and bind it to the BindingSource.
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
dataAdapter.Fill(table);
bindingSource1.DataSource = table;
}
}
EDIT: Added CargarDatos()
DoEvents() nearly always leads to a very bad place, and should pretty much always be avoided. The problem with this particular implementation is that your long running process (cargarDatos()) is going to block the UI thread until it completes, so DoEvents() will have no effect; it will not interrupt a method while it is executing (assuming cargarDatos() isn't doing something internally that would yield the thread). Also, creating and interacting with a UI control from some background thread is not valid here.
You should use a BackgroundWorker to perform the long running operation and raise status updates to the UI: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Categories