Blog Archives
C# Application Not Responding Issue on Long Running Threads
Undoubtedly many of you have encountered the dreaded “Application Not Responding” message, programmers and non-programmers alike.
In general, this is caused by the OS misinterpretation of long delays in form refresh, even though these can easily be caused by a long running process, or any running process longer than a couple seconds actually.
If this is happening in your application after clicking on a button or event fires, a quick solution would be the easy one-liner below:
Application.DoEvents();
However, to really understand why this happening, remember that all processes (may) contain multiple threads.
In the case of your typical quick shot solution that you put together in VS in no-time, you’re likely only using a single thread.
This thread is used not only to run the processes in your application, but also to update the form window including drawing the fields and various controls on the form.
The best solution to this would really be to create a separate thread and pass off the work of your long running function to this thread.
When the work is finished, it will update our main thread (the form thread) that it has completed which we can intercept with a delegate, and notify the user of success or failure.
See snippet below.
private BackgroundWorker hardworker; //this guys works hard! private System.Timers.Timer lazyworker; //notice how this could also be achieved with thread/timer protected void btnDoAlotOfWork_Click() { //lets see who accomplishes more! ClockInLazyWorker(); ClockInHardWorker(); } private void ClockInLazyWorker() { lazyworker = new System.Timers.Timer(); //not to be confused with forms timer lazyworker.Interval = 1000; //time in milliseconds. 10 seconds is lazy for a computer! lazyworker.Elapsed += new System.Timers.ElapsedEventHandler(LetsGetAMoveOnIt()); } private void ClockInHardWorker() { hardworker= new BackgroundWorker(); mCopier.DoWork += DoWork; //all the heavy lifting is done here mCopier.RunWorkerCompleted += WorkCompleted(); mCopier.WorkerSupportsCancellation = true; OnError += ProblemWithWork(); } //motivational function name made especially for our lazy worker private void LetsGetAMoveOnIt() { //do something that takes a long time //if the data you operate on is the same as used by another thread or the form thread, make sure you wrap this block in a synclock or use a bool to track when work is in process } //created separate function for the background worker, and also to avoid synclock issues since there are two new active threads private void DoWork() { //if the data you operate on is the same as used by another thread or the form thread, make sure you wrap this block in a synclock or use a bool to track when work is in process. same goes for timer. } private void WorkCompleted() { //all finished } private void ProblemWithWork() { //hopefully not an injury on the job! that's workmans comp! }
Alot more code than simply “Application.DoEvents”.. but do not underestimate the power of multiple threads!
AppActivate In C#
AppActivate, which many of you may recognize from VB activates a window for an application which is already running, bringing the window of that app to the foreground.
In C#, there is no exact equivalent to this function, which leaves two alternatives (see link at the bottom for code examples):
Option 1 (recommended):
Use SetForeGroundWindow, described in my similar post (scroll down a bit for the code examples).
Option 2:
Add a reference to Microsoft.VisualBasic and call Interactive.AppActivate.
Although Option 2 is a little simpler/faster if you are not familiar with or ready yet to delve into Win32 API, it is not a recommended solution as there can be potential side effects of this approach.
For code examples of both options, see my similarly titled post below and scroll through for code references.
References
Start a Process in the Foreground in C# .Net Without AppActivate https://ronniediaz.com/2011/05/03/start-a-process-in-the-foreground-in-c-net-without-appactivate/
Winforms Databinding
Snippets below have been condensed from their original sources for brevity. See references for original articles.
Dataset usage:
using System; using System.Data; using System.Data.SqlClient; namespace Microsoft.AdoNet.DataSetDemo { class NorthwindDataSet { static void Main() { string connectionString = GetConnectionString(); ConnectToData(connectionString); } private static void ConnectToData(string connectionString) { //Create a SqlConnection to the Northwind database. using (SqlConnection connection = new SqlConnection(connectionString)) { //Create a SqlDataAdapter for the Suppliers table. SqlDataAdapter adapter = new SqlDataAdapter(); // A table mapping names the DataTable. adapter.TableMappings.Add("Table", "Suppliers"); // Open the connection. connection.Open(); Console.WriteLine("The SqlConnection is open."); // Create a SqlCommand to retrieve Suppliers data. SqlCommand command = new SqlCommand( "SELECT SupplierID, CompanyName FROM dbo.Suppliers;", connection); command.CommandType = CommandType.Text; // Set the SqlDataAdapter's SelectCommand. adapter.SelectCommand = command; // Fill the DataSet. DataSet dataSet = new DataSet("Suppliers"); adapter.Fill(dataSet); // Create a second Adapter and Command to get // the Products table, a child table of Suppliers. SqlDataAdapter productsAdapter = new SqlDataAdapter(); productsAdapter.TableMappings.Add("Table", "Products"); SqlCommand productsCommand = new SqlCommand( "SELECT ProductID, SupplierID FROM dbo.Products;", connection); productsAdapter.SelectCommand = productsCommand; // Fill the DataSet. productsAdapter.Fill(dataSet); // Close the connection. connection.Close(); Console.WriteLine("The SqlConnection is closed."); // Create a DataRelation to link the two tables // based on the SupplierID. DataColumn parentColumn = dataSet.Tables["Suppliers"].Columns["SupplierID"]; DataColumn childColumn = dataSet.Tables["Products"].Columns["SupplierID"]; DataRelation relation = new System.Data.DataRelation("SuppliersProducts", parentColumn, childColumn); dataSet.Relations.Add(relation); Console.WriteLine( "The {0} DataRelation has been created.", relation.RelationName); } } static private string GetConnectionString() { // To avoid storing the connection string in your code, // you can retrieve it from a configuration file. return "Data Source=(local);Initial Catalog=Northwind;" + "Integrated Security=SSPI"; } } }
Binding DataGridView:
private void GetData(string selectCommand) { try { // Specify a connection string. Replace the given value with a // valid connection string for a Northwind SQL Server sample // database accessible to your system. String connectionString = "Integrated Security=SSPI;Persist Security Info=False;" + "Initial Catalog=Northwind;Data Source=localhost"; // Create a new data adapter based on the specified query. dataAdapter = new SqlDataAdapter(selectCommand, connectionString); // Create a command builder to generate SQL update, insert, and // delete commands based on selectCommand. These are used to // update the database. 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; // Resize the DataGridView columns to fit the newly loaded content. dataGridView1.AutoResizeColumns( DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader); } catch (SqlException) { MessageBox.Show("To run this example, replace the value of the " + "connectionString variable with a connection string that is " + "valid for your system."); } }
References:
MSDN, “DataSet Class”, http://msdn.microsoft.com/en-us/library/system.data.dataset.aspx
MSDN, “How to: Bind Data to the Windows Forms DataGridView Control”, http://msdn.microsoft.com/en-us/library/fbk67b6z.aspx