How to Deal with Asynchronous SQL Queries from Non-UI Threads - c#

All, I have successfully used ADO.NET to make use of asynchronous SQL queries similar to the example below. In the example shown the method ExecNonQuery is being invoked from the UI thread. This works well, but I wondered how I would handle the callback if I were to call ExecNonQuery from a non-UI thread?
Note. Clearly, in such a case I would amend ExecNonQuery, so that such things as this.toolStripStatusLabel1.Text were dealt with accordingly, or removed.
public bool ExecNonQuery(string strCmd, string strUserMsg = "")
{
try
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = strCmd;
cmd.CommandTimeout = 0;
bIsExecuting = true;
AsyncCallback callback = new AsyncCallback(HandleCallback);
cmd.BeginExecuteNonQuery(callback, cmd);
return true;
}
catch (Exception Ex)
{
bIsExecuting = false;
this.toolStripStatusLabel1.Text = String.Format("Ready (last error: {0})", Ex.Message);
if (conn != null)
conn.Close();
}
return false;
}
private delegate void DisplayInfoDelegate(string Text);
private void HandleCallback(IAsyncResult result)
{
try
{
// Retrieve the original command object, passed
// to this procedure in the AsyncState property
// of the IAsyncResult parameter.
SqlCommand command = (SqlCommand)result.AsyncState;
int rowCount = command.EndExecuteNonQuery(result);
string rowText = " rows affected.";
if (rowCount == 1)
rowText = " row affected.";
rowText = rowCount + rowText;
// Call the procedure from the form's thread.
DisplayInfoDelegate del = new DisplayInfoDelegate(DisplayResults);
this.Invoke(del, rowText);
}
catch (Exception ex)
{
// Because you are now running code in a separate thread,
// if you do not handle the exception here, none of your other
// code catches the exception.
// You can create the delegate instance as you
// invoke it, like this:
this.Invoke(new DisplayInfoDelegate(DisplayResults),
String.Format("Ready(last error: {0}", ex.Message));
}
finally
{
bIsExecuting = false;
if (conn != null)
conn.Close();
}
}
private void DisplayResults(string Text)
{
this.toolStripStatusLabel1.Text = Text;
this.toolStripProgressBar1.Style = ProgressBarStyle.Blocks;
this.toolStripProgressBar1.Value = 100;
}
Thanks for you time.

It makes no difference to your callback which thread runs ExecNonQuery - HandleCallback will still be run on a thread pool thread.
You have already spotted the change you need to make: don't access UI controls directly in ExecNonQuery if it is not being run on the UI thread.
Nick

Related

Asynchronously updating multiple row updates 1/4 of rows instantly and then waits

I have a code to asynchronously update multiple rows in SQL Server's table. I tested it on updating 540 rows and 144 rows are updated in the table instanly, then it waits for about 5 minutes and then the rest is updated. At least this is how it looks when I check for updated rows with SELECT.. I'm wondering why is that.
The whole thing is triggered by button's click:
DialogResult res = MessageBox.Show($"Znaleziono {num} pasujących maszyn. Czy chcesz zaktualizować priorytet maszyny danymi z pliku?", "Potwierdź", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if(res == DialogResult.Yes)
{
await UpdatePriority();
MessageBox.Show("Updated!");
Here's UpdatePriority method that asynchronously call place.Edit() method for all places in the list of items:
public async Task<string> UpdatePriority()
{
List<Task<string>> UpdateTasks = new List<Task<string>>();
try
{
foreach (Place p in Items.Where(i => i.IsUpdated==true))
{
UpdateTasks.Add(Task.Run(()=> p.Edit()));
}
string response = "OK";
IEnumerable<string> res = await Task.WhenAll<string>(UpdateTasks);
}
catch (Exception ex)
{
throw;
}
return "Nie udało się zaktualizować danych żadnego zasobu..";
}
And here is Edit() method of place object. It basically updates place data in SQL server table:
public async Task<string> Edit()
{
string iSql = #"UPDATE JDE_Places
SET Priority=#Priority
WHERE PlaceId=#PlaceId";
string msg = "OK";
using (SqlCommand command = new SqlCommand(iSql, Settings.conn))
{
command.Parameters.AddWithValue("#PlaceId", PlaceId);
command.Parameters.AddWithValue("#Priority", Priority);
int result = -1;
try
{
result = await command.ExecuteNonQueryAsync();
IsUpdated = false;
}
catch (Exception ex)
{
msg = $"Wystąpił błąd przy edycji zasobu {Name}. Opis błędu: {ex.Message}";
}
}
return msg;
}
And here's Settings conn property that serves as reusable connection object:
public static class Settings
{
private static SqlConnection _conn { get; set; }
public static SqlConnection conn
{
get
{
if (_conn == null)
{
_conn = new SqlConnection(Static.Secrets.ConnectionString);
}
if (_conn.State == System.Data.ConnectionState.Closed || _conn.State == System.Data.ConnectionState.Closed)
{
try
{
_conn.Open();
}
catch (Exception ex)
{
MessageBox.Show("Nie udało się nawiązać połączenia z bazą danych.. " + ex.Message);
}
}
return _conn;
}
}
}
I realize it's probably better to keep the connection within using statement (instead of reusing it), but when I added it to place.Edit() method it worked even slower (and unreliably).
UPDATE: I ran few tests more and the time they took to add 540 rows varied from 15 seconds to 400 seconds.. Then I just changed result = await command.ExecuteNonQueryAsync() to result = command.ExecuteNonQuery() in Edit() of place object, ran few tests more, and all finished under 10 seconds! I don't know why async version of ExecuteNonQuery() was so much worse than non-async one, though. Single Edit() method was taking around 0,1 sec with ExecuteNonQuery() and 1 - 400 seconds with ExecuteNonQueryAsync(). Here are logs: ExecuteNonQuery() ExecuteNonQueryAsync()
Your issue here is your Settings class. You're essentially trying to use the same SqlConnection object in multiple Sqlcommands. SqlConnection is not threadsafe when used like this. You end up with multiple commands because your code is non-blocking and async. That is what is causing your code the "wait" (or deadlock). This is why when you run it sync (without the ExecuteNonQueryAsync, etc.) it works correctly.
You don't need this object at all anyway. ADO.Net handles connection pooling for you, so there is no advantage in re-using the same SqlConnection. Just create a new one for each SqlCommand:
public async Task<string> Edit()
{
using (SqlConnection conn = new SqlConnection(...))
using (SqlCommand command = new SqlCommand(iSql, conn))
{
...
}
}
and you should find that your "wait" goes away.

Progressbar on Waitform freezes using a Task run on another Thread

I have an application which gets data from Excel Sheets using OleDb.
On the Form I have controls so the user can filter the data to his needs.
For example, FileSize, UserID, Rootpath etc. This works perfectly.
After final selection the User has to press an "update" Button so I can filter the data based on his input. The result will be shown in a DataGridView.
However, since the Data on the Excel Sheets varies a lot, I used to have a ProgressBar on a second Form (Waitform) or make the DataGridView invisible while the ProgressBar on the UI is visible during the non-UI-Task (Data Collection).
I do know that I should use a Task or a Thread (or a BackGroundWorker) to keep the UI responsive.
That being said, it still freezes my whole application.
//Update Button which uses all the userdefined filters
private async void updateButton_Click(object sender, EventArgs e)
{
WaitBarDatagrid.Visible = true; //Progressbar is called WaitBarDatagrid
WaitBarDatagrid.Style = ProgressBarStyle.Marquee;
WaitBarDatagrid.MarqueeAnimationSpeed = 30;
dataGridView1.Visible = false;
await Task.Run(() => QueryToExcel());
dataGridView1.DataSource = FileInfos;
WaitBarDatagrid.Visible = false;
dataGridView1.Visible = true;
}
private void QueryToExcel()
{
this.Invoke((MethodInvoker)delegate ()
{
string fSize;
if (FileSizeComboBox.Text == "All Data")
{ fSize = "0"; }
else if (FileSizeComboBox.Text == "> 1 MB")
{ fSize = "1000"; } // 1MB = 1000kB
else if (FileSizeComboBox.Text == "> 10 MB")
{ fSize = "10000"; } // 10MB = 10.000kB
else if (FileSizeComboBox.Text == "> 100 MB")
{ fSize = "100000"; } // 100MB = 100.000kB
else if (FileSizeComboBox.Text == "> 1 GB")
{ fSize = "1000000"; } // 1 GB = 1000.000 kB
else
fSize = "0";
// The following ensures that all possibilities of User Definition are covered
string user = "";
string size = "";
string sep = ""; //Seperator
if (!string.IsNullOrEmpty(UserTextbox.Text))
{
user = $"[UserID] = '{UserTextbox.Text}'";
sep = "AND";
}
if (!string.IsNullOrEmpty(FileSizeComboBox.Text))
{
size = $"{sep} [File Size] >= {fSize}";
sep = "AND";
}
//Final Where CLAUSE based on User Input
//string command = $#"{user} {size}{sep} [Date] <= {DateBox.Value.ToOADate()}";
string command = $#"{user} {size} {sep} [Date] <= {DateBox.Value.ToOADate()}";
//Call Data from Excel
string connectionString = GetConnectionString(Datapath + RootCombobox.Text);
string query = $#"SELECT * from [FileInfos$] WHERE ({command})";
DataTable dt = new DataTable();
using (OleDbConnection conn = new OleDbConnection(connectionString))
{
conn.Open();
using (OleDbDataAdapter dataAdapter = new OleDbDataAdapter(query, conn))
{
try
{
dataAdapter.Fill(dt);
FileInfos = dt;
}
catch (System.Data.OleDb.OleDbException ex)
{
MessageBox.Show(ex.ToString());
}
}
}
});
}
So far I also have tried to assign the values of the Userinputs to global variables and it will be changed in their correspondent events. However, even with invoke my UI freezes. Where does it come from?
The QueryToExcel() method is supposed to queue the work to run on a ThreadPool Thread, to let UI Thread continue its own work without freezing.
But you notice that the UI freezes anyway, saying:
even with invoke my UI freezes
It's invoking the UI thread from another thread that freezes it.
Doing work on another thread is about not using the UI thread. If we invoke back the UI thread from a worker thread, the effect is lost (or partially lost, annoying in any case).
You're also using Invoke() instead of BeginInvoke(). The latter is executed asynchronously: it returns immediately and can prevent deadlocks if the control invoked is busy or otherwise unreachable/unresponsive.
It won't prevent the UI from stuttering at times, anyway.
Looking at the code you presented here, it appears that there's no need to invoke the UI thread at all: the secondary thread just needs the properties values of some controls and then assigns a DataTable to a field.
It is then possible to pass to this method the required values as arguments, assigning the Controls' properties to some variables or to the properties of a class (so it's easier to understand what the arguments contain).
the worker method could be changed in
private DataTable QueryToExcel(string[] paramArray)
Or
private DataTable QueryToExcel(SomeClass values)
and can be called as:
private async void updateButton_Click(object sender, EventArgs e)
{
var dt = await Task.Run(() => QueryToExcel(values));
Or
dataGridView1.DataSource = await Task.Run(() => QueryToExcel(values));
}
In QueryToExcel() to Excel:
Access the values parameter to setup the query or other processing.
Create the DB Connection and fill a DataTable/DataSet.
Dispose of all the disposable objects created (Connection/DataAdapter etc)
Return a DataTable
Your code basically jumps to a non-UI thread and then jumps back to the UI - it's almost as if you never left the UI thread.
What you need to do is do all of your UI work on the UI thread and only do non-UI work on the other thread.
Try this code:
// Define other methods and classes here
//Update Button which uses all the userdefined filters
private async void updateButton_Click(object sender, EventArgs e)
{
WaitBarDatagrid.Visible = true; //Progressbar is called WaitBarDatagrid
// WaitBarDatagrid.Style = ProgressBarStyle.Marquee;
// WaitBarDatagrid.MarqueeAnimationSpeed = 30;
dataGridView1.Visible = false;
string fSize;
if (FileSizeComboBox.Text == "All Data")
{ fSize = "0"; }
else if (FileSizeComboBox.Text == "> 1 MB")
{ fSize = "1000"; } // 1MB = 1000kB
else if (FileSizeComboBox.Text == "> 10 MB")
{ fSize = "10000"; } // 10MB = 10.000kB
else if (FileSizeComboBox.Text == "> 100 MB")
{ fSize = "100000"; } // 100MB = 100.000kB
else if (FileSizeComboBox.Text == "> 1 GB")
{ fSize = "1000000"; } // 1 GB = 1000.000 kB
else
fSize = "0";
// The following ensures that all possibilities of User Definition are covered
string user = "";
string size = "";
string sep = ""; //Seperator
if (!string.IsNullOrEmpty(UserTextbox.Text))
{
user = $"[UserID] = '{UserTextbox.Text}'";
sep = "AND";
}
if (!string.IsNullOrEmpty(FileSizeComboBox.Text))
{
size = $"{sep} [File Size] >= {fSize}";
sep = "AND";
}
//Final Where CLAUSE based on User Input
//string command = $#"{user} {size}{sep} [Date] <= {DateBox.Value.ToOADate()}";
string command = $#"{user} {size} {sep} [Date] <= {DateBox.Value.ToOADate()}";
await Task.Run(() => QueryToExcel(command, RootCombobox.Text));
dataGridView1.DataSource = FileInfos;
WaitBarDatagrid.Visible = false;
dataGridView1.Visible = true;
}
private void QueryToExcel(string command, string RootCombobox_Text)
{
//Call Data from Excel
string connectionString = GetConnectionString(Datapath + RootCombobox_Text);
string query = $#"SELECT * from [FileInfos$] WHERE ({command})";
DataTable dt = new DataTable();
using (OleDbConnection conn = new OleDbConnection(connectionString))
{
conn.Open();
using (OleDbDataAdapter dataAdapter = new OleDbDataAdapter(query, conn))
{
try
{
dataAdapter.Fill(dt);
this.Invoke((MethodInvoker)delegate () { FileInfos = dt; });
}
catch (System.Data.OleDb.OleDbException ex)
{
this.Invoke((MethodInvoker)delegate () { MessageBox.Show(ex.ToString()); });
}
}
}
}
It's untested, but it should be close. Note that non of the UI elements are accessed or updated on any non-UI thread.

How to retry connecting to database after internet connection has been switched off after some time

i have an notification application built in c# for notifying any change in database . the application runs in the background . But the problem is after the start of the application if the internet is switched off the application throws an SQLException . i have used try catch to handle the exception . but i want my application to try connecting the database and when the connection is established it returns to the main code .
try
{
using (SqlConnection connection =
new SqlConnection(GetConnectionString()))
{
//i want to return here when the connection is reestablished
using (SqlCommand command =
new SqlCommand(GetListenerSQL(), connection))
{
connection.Open();
// Make sure we don't time out before the
// notification request times out.
command.CommandTimeout = NotificationTimeout;
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
messageText = System.Text.ASCIIEncoding.ASCII.GetString((byte[])reader.GetValue(13)).ToString();
// Empty queue of messages.
// Application logic could parse
// the queue data and
// change its notification logic.
}
object[] args = { this, EventArgs.Empty };
EventHandler notify =
new EventHandler(OnNotificationComplete);
// Notify the UI thread that a notification
// has occurred.
this.BeginInvoke(notify, args);
}
}
}
catch(SqlException e)
{
}
is it possible to do it without goto statement . i would prefer avoiding the goto statement .
I would move the retry logic out of the query method. I've seen a good retry library somewhere, but I can't find it just now.
public void StartListener()
{
var message = GetMessage();
//process message in some way
object[] args = { this, EventArgs.Empty };
EventHandler notify = OnNotificationComplete;
this.BeginInvoke(notify, args);
}
private const int TimeoutStep = 2000;
private const int MaxTimeout = 10000;
private string GetMessage(int timeout = 0)
{
//prevent loop of endless retries
if (timeout >= MaxTimeout)
{
//optional: define your own Exception class
throw new MaxTimeoutException();
}
try
{
Thread.Sleep(timeout);
return GetMessageFromDatabase();
}
catch (SqlException ex)
{
//log ex in debug mode at least
return GetMessage(timeout + TimeoutStep);
}
}
private string GetMessageFromDatabase()
{
string message = null;
using (var connection = new SqlConnection(GetConnectionString()))
{
using (var command = new SqlCommand(GetListenerSQL(), connection))
{
connection.Open();
command.CommandTimeout = NotificationTimeout;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
message = System.Text.ASCIIEncoding.ASCII.GetString((byte[])reader.GetValue(13));
}
}
}
}
return message;
}
from the suggestion of #Archer i got the solution . in the catch block i call the method again which uses this connection after some suitable time . Something like
public void StartListener()
{
try
{
using (SqlConnection connection =
new SqlConnection(GetConnectionString()))
{
//i want to return here when the connection is reestablished
using (SqlCommand command =
new SqlCommand(GetListenerSQL(), connection))
{
connection.Open();
// Make sure we don't time out before the
// notification request times out.
command.CommandTimeout = NotificationTimeout;
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
messageText = System.Text.ASCIIEncoding.ASCII.GetString((byte[])reader.GetValue(13)).ToString();
// Empty queue of messages.
// Application logic could parse
// the queue data and
// change its notification logic.
}
object[] args = { this, EventArgs.Empty };
EventHandler notify =
new EventHandler(OnNotificationComplete);
// Notify the UI thread that a notification
// has occurred.
this.BeginInvoke(notify, args);
}
}
}
catch(SqlException e)
{
Thread.Sleep(2000);
StartListener();
}
}
If its an failure you should call an timer and the timer should call the backgroundworker.
Write the Functionality to check the connection in the backgroundworkers. if it goes for true you should stop the timer. And call the usual process

how to make progress bar work with functions in button event

i am new in C# so please be patient with me .
i want to make progress bar work with any functions i make in my program
i have class to check if INTERNET available and the connection of database status
and i have "progressBar1" , style is "Marquee"
i just want to indicate that there is a process work "Function" in the program and i don't need to have step or timer to increment it
just make the progress work until the function finish its code and the functions will work in button event (when i push button)
this is my code
class checkInternet
{
[DllImport("wininet.dll")]
private extern static bool InternetGetConnectedState(out int Description, int ReservedValue);
public bool checkInternetAvailable()
{
int Desc;
bool result = false;
if (InternetGetConnectedState(out Desc, 0) == true)
{
try
{
dbConnection StartConn = new dbConnection();
SqlConnection MyConnetion = StartConn.GetConnection();
MyConnetion.Open();
if (MyConnetion.State == ConnectionState.Open)
{
result = true;
}
MyConnetion.Close();
}
catch (Exception)
{
result = false;
MessageBox.Show("The database connection does not available, May be because of this reasons: \n\n1- there is a new version of the program avalible. \n2- database has some maintenance. \n\n Please check later :)", "Conection status");
}
}
else
{
result = false;
MessageBox.Show("No internet connection avalible , Please check later :) \nThanks.", "Conection status");
}
return result;
}
}
and this is what i have in my button event
private void button1_Click(object sender, EventArgs e)
{
checkInternet check = new checkInternet();
progressBar1.Value = 0;
do
{
progressBar1.PerformStep();
} while (check.checkInternetAvailable());
}
how can i implement that ?
thanks
As I understand you want user to see progressbar while your check connection task executes in background. checkInternetAvailable would be your background operation and I wouldn't suggest showing messages directly form it. Instead return a custom struct :
public struct ConnectionCheckResult
{
public bool Success;
public string Message;
}
And this will be your button click event handler :
private void button1_Click(object sender, EventArgs e)
{
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.Visible = true;
//add code here to be executed on UI thread before connection check
Task.Run(new Action(() =>
{
//Task.Run this code on the thread pool instead of your UI thread. So your code is checking connection while progress bar is still rendering
ConnectionCheckResult res = new checkInternet().checkInternetAvailable();
this.Invoke(new Action(() =>
{
//this.Invoke executes following delegate on UI thread. All UI changes - like progressBar1.Visible = false; need to be made in UI thread.
//add code here to be executed on the UI thread after connection check.
progressBar1.Visible = false;
if (!string.IsNullOrEmpty(res.Message))
MessageBox.Show(res.Message);
}));
}));
//add code to be executed on UI thread at the same time as connection check
}
I know multi-threading is difficult to wrap your head around at first, here's good tutorial with code samples.
Also when your progressbar style is Marquee you don't need to call PerformStep. It will just roll by itself.
EDIT: You should also modify checkInternetAvailable() like so :
public ConnectionCheckResult checkInternetAvailable()
{
int Desc;
ConnectionCheckResult result = new ConnectionCheckResult();
if (InternetGetConnectedState(out Desc, 0) == true)
{
try
{
dbConnection StartConn = new dbConnection();
SqlConnection MyConnetion = StartConn.GetConnection();
MyConnetion.Open();
if (MyConnetion.State == ConnectionState.Open)
{
result.Success = true;
}
MyConnetion.Close();
}
catch (Exception)
{
result.Success = false;
result.Message = "The database connection does not available, May be because of this reasons: \n\n1- there is a new version of the program available. \n2- database has some maintenance. \n\n Please check later :)";
}
}
else
{
result.Success = false;
result.Message = "No internet connection available , Please check later :) \nThanks.";
}
return result;
}

Progress bar not working on button click C# windows application

I want to synchronize my local and web database so i have written a stored procedure using linked server. My stored procedure executes fine and data synchronization is successful but the procedure takes around 7-10 minutes to get executed. The exact timing cannot be determined. So whenever the procedure runs on my windows application then the page seems as if it has become unresponsive though the process is still going on.
So i am having a "Data Sync" button on my page on click of which i want the progress bar to display the progress of the stored procedure. For the time being I am taking the average of last few execution timings to define the time duration for which the stored procedure runs. Now the problem is that when i click on the data sync button then the progress bar doesn't work. Kindly help me with this issue.
My code is as follows:-
namespace RMS
{
public partial class DataSync : Form
{
connection con = new connection();
SqlCommand cmd = new SqlCommand();
static int rowCount;
static int syncTime;
static int timeSlice;
public DataSync()
{
InitializeComponent();
}
private void btnDataSync_Click(object sender, EventArgs e)
{
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync();
try
{
con.GetConnectLive();
con.GetConnect();
if (con.CnLive.State == ConnectionState.Open)
{
MessageBox.Show("Connection to Live Server Successful!!!...Data Synchronisation may take several minutes so do not cancel the operation while in execution mode");
btnDataSync.Enabled = false;
btnDataSync.Text = "Please Wait...";
string Str = "RMS_LocalToLive";
cmd = new SqlCommand(Str, con.Cn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 1200;
rowCount = cmd.ExecuteNonQuery();
if (rowCount > -1)
{
MessageBox.Show("Total no. of rows synchronised = " + rowCount);
btnDataSync.Text = "Success";
}
else
{
MessageBox.Show("Data Synchronisation couldn't be completed because of connection problem... Please try again!!!");
}
}
else
{
MessageBox.Show("Unable to connect to Live Server...Please check your internet connection and try again!!!");
}
con.GetDisConnect();
con.GetDisConnectLive();
}
catch (Exception ex)
{
MessageBox.Show("Please check your internet connection and try again!!!");
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
con.GetConnect();
string Str = "RMS_DataSyncTime";
cmd = new SqlCommand(Str, con.Cn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 1200;
syncTime = Convert.ToInt32(cmd.ExecuteScalar().ToString());
timeSlice = syncTime / 100;
con.GetDisConnect();
}
catch (Exception ex)
{
MessageBox.Show("Unable to retrieve last Data Synchronisation Timing");
}
for (int i = 1; i <= synctime; i=i+timeslice)
{
Thread.Sleep(timeslice);
// Report progress.
backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Change the value of the ProgressBar to the BackgroundWorker progress.
progressBar1.Value = e.ProgressPercentage;
// Set the text.
this.Text = e.ProgressPercentage.ToString() + "% Completed";
}
private void DataSync_Load(object sender, EventArgs e)
{
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgse)
{
}
}
}
The main issue here is that, while you are executing your progress bar updates in the BackgroundWorker's thread, the ReportProgress() updates never make it to the UI thread, because you've blocked that thread with the main SQL operation.
Instead of doing that, you should do something more like this:
private void btnDataSync_Click(object sender, EventArgs e)
{
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync();
btnDataSync.Enabled = false;
btnDataSync.Text = "Please Wait...";
bool success = false;
try
{
// Execute the query asynchronously
success = await Task.Run(() => ExecuteLocalToLive());
}
catch (Exception ex)
{
MessageBox.Show("Please check your internet connection and try again!!!");
}
btnDataSync.Enabled = true;
btnDataSync.Text = success ? "Success" : "Failure";
}
private bool ExecuteLocalToLive()
{
bool success = false;
con.GetConnectLive();
con.GetConnect();
if (con.CnLive.State == ConnectionState.Open)
{
MessageBox.Show("Connection to Live Server Successful!!!...Data Synchronisation may take several minutes so do not cancel the operation while in execution mode");
string Str = "RMS_LocalToLive";
cmd = new SqlCommand(Str, con.Cn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 1200;
rowCount = cmd.ExecuteNonQuery();
if (rowCount > -1)
{
MessageBox.Show("Total no. of rows synchronised = " + rowCount);
success = true;
}
else
{
MessageBox.Show("Data Synchronisation couldn't be completed because of connection problem... Please try again!!!");
}
}
else
{
MessageBox.Show("Unable to connect to Live Server...Please check your internet connection and try again!!!");
}
con.GetDisConnect();
con.GetDisConnectLive();
return success;
}
I have rearranged the code that handles the button state and text, so that it's still executed in the UI thread where it belongs, even though the method itself is not. You also never appeared to set the button back to the enabled state; it's not clear to me whether that was intentional or not, so I went ahead and added a line to do that.
Finally, I will strongly recommend you figure out a better way to report status to the user than the calls to MessageBox.Show() you have now. The biggest issue is that you don't even start doing any work until after the user dismisses the initial message, which immediately puts your progress bar out of sync with the actual work. But it's also better to keep all your UI in the UI thread, and to keep UI separate from non-UI logic (i.e. the SQL operation).

Categories