When developing applications with user interfaces (UIs) that perform background operations, managing the interaction between the UI thread and worker threads is crucial. Common scenarios involve controlling background tasks (start, pause, stop) and displaying their progress on the UI. A typical issue arises when lengthy operations, like database access, are performed on the UI thread, leading to unresponsive applications.
This article categorizes UI feedback during background tasks into two main types:
**1. Modal Waiting:**In this approach, the UI remains completely blocked until the background operation concludes. No user interaction is permitted on the main form.
Diagram illustrating modal waiting and UI enteraction.
**2. Non-Modal Waiting:**This method allows the UI to remain interactive while a background task runs. A separate, non-blocking dialog box typicaly displays the progress.
Diagram illustrating non-modal waiting and UI interaction.
The following code examples demonstrate implementations for both modal and non-modal waiting forms.
Modal Waiting Implementation
The frmWait form handles modal background operations. It displays a progress bar and updates a label with the current status. The DoWait method initiates the background task and then blocks the UI thread by calling ShowDialog().
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
public partial class frmWait : Form
{
private bool keepRunning = true;
private List<string> processedItems = new List<string>();
private ProgressBar progressBar;
private Label statusLabel;
private Button cancelButton;
public frmWait()
{
InitializeComponent(); // Assume this sets up progressBar, statusLabel, cancelButton
this.cancelButton.Click += new EventHandler(CancelButton_Click);
}
public object PerformBackgroundWork(object parameter)
{
int workCount = (int)parameter;
progressBar.Maximum = workCount;
// Execute the time-consuming operation asynchronously
Func<List<string>> backgroundTask = () =>
{
Thread.Sleep(1000); // Initial delay
for (int i = 0; i < workCount; i++)
{
if (!keepRunning)
{
break;
}
string timestamp = DateTime.Now.ToLongTimeString();
processedItems.Add(timestamp);
// Update UI elements safely using Invoke
this.Invoke((Action)delegate
{
if (!this.IsDisposed)
{
progressBar.Value = i + 1; // +1 because loop starts at 0
statusLabel.Text = $"Processing item: \"{timestamp}\"";
}
});
Thread.Sleep(500); // Simulate work
}
return processedItems;
};
backgroundTask.BeginInvoke(new AsyncCallback(OnWorkCompleted), null);
// Block the UI thread until the dialog is closed
ShowDialog();
return processedItems;
}
private void OnWorkCompleted(IAsyncResult ar)
{
// If the operation wasn't cancelled, close the dialog gracefully
if (keepRunning)
{
this.Invoke((Action)delegate { DialogResult = DialogResult.OK; });
}
}
private void CancelButton_Click(object sender, EventArgs e)
{
keepRunning = false; // Signal the background thread to stop
DialogResult = DialogResult.Cancel; // Close the modal dialog
}
// Load event handler (can be empty or used for initialization)
private void frmWait_Load(object sender, EventArgs e) { }
}
To use this modal form:
using (frmWait modalWaiter = new frmWait())
{
// Execute background work and display the modal form
List<string> results = modalWaiter.PerformBackgroundWork(50) as List<string>;
MessageBox.Show($"Processed {results.Count} items.");
// UI thread continues execution after the modal form is closed
}
Non-Modal Waiting Implementation
The frmNoWait form implements non-modal background operations. It displays progress but allows the main UI to remain active. Multiple instances of this form can be shown simultaneously.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
public partial class frmNoWait : Form
{
private bool isRunning = true;
private List<string> gatheredData = new List<string>();
private ProgressBar progressIndicator;
private Label messageLabel;
private Button closeButton;
public frmNoWait()
{
InitializeComponent(); // Assume this sets up progressIndicator, messageLabel, closeButton
this.closeButton.Click += new EventHandler(CloseButton_Click);
}
private void BackgroundTaskCallback(IAsyncResult ar)
{
// ar.AsyncState could contain results if passed during BeginInvoke
if (isRunning)
{
// Close the form after the background task completes successfully
this.Invoke((Action)delegate
{
if (!this.IsDisposed)
{
Close();
}
});
}
}
public void InitiateNonModalWork(int itemCount)
{
progressIndicator.Maximum = itemCount;
// Define and start the background task
Action workerAction = () =>
{
try
{
Thread.Sleep(1000); // Initial delay
for (int i = 0; i < itemCount; i++)
{
if (!isRunning)
{
break;
}
string currentTimestamp = DateTime.Now.ToLongTimeString();
gatheredData.Add(currentTimestamp);
// Update UI safely
this.Invoke((Action)delegate
{
if (!this.IsDisposed)
{
progressIndicator.Value = i + 1;
messageLabel.Text = $"Loading string \"{currentTimestamp}\"";
}
});
Thread.Sleep(500); // Simulate task duration
}
}
catch (Exception ex)
{
// Handle exceptions appropriately
Console.WriteLine($"Error during background work: {ex.Message}");
}
};
workerAction.BeginInvoke(new AsyncCallback(BackgroundTaskCallback), gatheredData); // Pass data as state if needed
// Display the form without blocking the UI thread
Show();
}
private void frmNoWait_Load(object sender, EventArgs e)
{
// Example of adding unique identifiers to multiple instances
this.Text += $" Instance {Form1.InstanceCounter++}";
}
private void CloseButton_Click(object sender, EventArgs e)
{
Close(); // User initiated closure
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
isRunning = false; // Ensure background thread knows the form is closing
}
}
// Assuming Form1 has a static counter for unique form instances
public static class Form1
{
public static int InstanceCounter = 1;
}
To use this non-modal form:
frmNoWait nonModalWaiter = new frmNoWait();
nonModalWaiter.InitiateNonModalWork(50);
// The UI thread continues execution immediately
The OnAsync (or BackgroundTaskCallback in the refactored example) method is responsible for signaling the completion of the background task and potentially closing the non-modal form.