Avoid UI freezes at all costs, if possible
Join the DZone community and get the full member experience.
Join For FreeApplications tend to perform various resource-consuming operations and there is not much we as developers can change about that. In fact, developers don’t want to change that – mainly, because that is the purpose of those applications.
Introduction
If the application is running as a service, then the methods described in this article don’t apply to it - basically because services don't have an independent UI whatsoever. This article applies to client desktop applications that are running outside the console with a graphical UI (WinForms and WPF specifically).
To clearly demonstrate the impact of a resource-consuming process on the application, here is a real example of a method that can cause the UI to freeze:
void GetList()
{
var cities = new List<string>();
XmlDocument doc = new XmlDocument();
try
{
doc.Load("http://www.webservicex.net/uszip.asmx/GetInfoByState?USState=TX");
foreach (XmlNode node in doc.SelectSingleNode("NewDataSet"))
{
XmlNode childNode = node.SelectSingleNode("CITY");
string city = childNode.InnerText;
if (!cities.Contains(city))
{
cities.Add(city);
}
}
}
catch
{
MessageBox.Show("Error fetching data from server.", "Application", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
Via this method I am able to retrieve the full list of cities registered for a specific US state (Texas in this testing case). Roughtly saying, it depends on the service performance, but is also stopping the UI from responding while it is retrieving the data. This is not the most pleasant experience for end-users and if it continues for a little while, it might end up with the user terminating the application the “cold” way by killing the process. And there shouldn't be a long explanation on what a "cold" application termination might cause - at least the current data will be lost or become corrupt.
Users tend not to wait much once the UI becomes unresponsive.
To avoid this, it would be reasonable to move the resource-consuming process in another thread. That way, the UI thread will remain responsive to user input, but the process will be running in the background.
There are two ways to implement this. The first one is by using the regular Thread class. The second one is by using BackgroundWorker.
BackgroundWorker
If you are going to use BackgroundWorker, there are several “benefits” you gain. First of all, it offers easier UI communication from the background thread. This can be handled via the ProgressChanged and RunWorkerComplete event handlers.
The main method that actually performs all he background work is DoWork. Applied to my method, I would have to create a new instance of BackgroundWorker, create a DoWork event handler and call the method from there:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
void worker_DoWork(object sender, DoWorkEventArgs e)
{
GetList();
}
Then, you can track the execution of the background process by checking the IsBusy property, which will tell the developer whether the process is still running or not.
Once the process is finished, RunWorkerComplete is automatically triggered (in case it is set) – you can push notifications via this method or perform some finishing tasks.
NOTE: The thread started by a BackgroundWorker is a background thread.
Thread
If you are going to use the Thread class to manage an operation, you will have to manually handle the thread interaction between the UI thread and the one started for a specific task. However, one interesting thing about the Thread class is that it offers you control over the thread priorities. Although this might be a risky practice due to the way Windows prioritizes and handles threads, this might be important to implement when you are aware of the consumed resources and the impact on other threads.
To move a process to a background thread via the Thread class, you could use the following code (structure might change depending on the passed method):
Thread t = new Thread(new ThreadStart(GetList));
t.IsBackground = true;
t.Start();
I am explicitly setting the thread as a background one. What is the difference, you might ask? Foreground threads basically decide whether the process continues to run or not. If there are no running foreground threads, then the process is automatically killed. This, however, does not apply to background threads.
Setting my thread to be a background one is a good idea since while it is executing, the user might decide to close the application. If it remains as a foreground thread, then the UI thread will terminate, but the application will still be waiting for this thread to complete and that is not something you’d want.
Conclusion
Using either of the above methods will ensure that your application will provide a decent user experience and won’t cause frustration from the client’s side once a high processing point is hit. Although threading is one topic several of developer groups try to avoid unless necessary, I would say that implementing a threaded application brings more benefits than risks. With a little bit more effort on the developer's side, the end-user will avoid frustration later on when something could go wrong with an operation.
Opinions expressed by DZone contributors are their own.
Comments