You are right. If you have a WinForms application, and you need to do some lengthy calculations, it is a good idea to start a separate thread to do this. In the mean time your user interface thread is free to do other things, like react on buttons, update the display and show the progress of the lengthy calculations.
A fairly outdated method to do this, was to start the BackGroundWorker class. Although this method works fine, people nowadays tend to use async-await for this, because it is simpler to use.
Usually you see async methods when somewhere deep inside the procedure the thread must wait idly for another process to finish: fetch data from a database; write data to a hard disk; read information from the internet, etc. If you use async-await, then your thread won't wait idly, but goes up the call stack to see if one of the callers is not awaiting. This way your user interface will still be responsive, provided that the thread is not busy doing calculations.
The standard method of async-await in WinForms is something like this:
private async void Button1PressedAsync(object sender, ...)
{
Button button = (Button)sender;
button.Enabled = false;
this.ShowProgress();
// Start the Task, if you can do something else, don't await yet
Task<string> taskFetchData = this.FetchDataAsync();
DoSomethingElse();
// wehtn you need the result of the task: await for it
string fetchedData = await taskFetchData;
this.ShowFetchedData(fetchedData);
this.HideProgress();
button.Enabled = true;
}
private async Task<string> FetchDataAsync()
{
using (var textReader = System.IO.File.OpenText(this.FileName))
{
return await textReader.ReadToEndAsync();
}
}
What we see here:
- Every function who wants to use async-await must be declared async
- Return
Task<TResult>
instead of TResult
, return Task
instead of void
. Only exception: event handlers return void instead of Task: no one will have to await an event handler. In Forms you usually see them as button clicks, mouse clickes, menu selections etc.
- Start the async task, if you don't need the result immediately, don't await yet,
but do other useful stuff.
- Only when you need the result of the task, or at last just before you return await the task, so you are certain that the task is finished when you are finished. Alternative: return the task and let your caller await it.
- Because the thread that returns after the
await
has the same "context", this thread can be regarded as the UI thread: it can update user interface elements. No need for IsInvokeRequired
etc.
Back to your question
You want to keep you UI responsive while the data is being fetched from the database. If the database management system can do most of the work, you are lucky, a simple async-await is enough.
It depends a bit on the method that you use to communicate with the database. You will have to search for async methods. I'll give an example using SQL:
private async Task<List<Order>> FetchOrderOfCustomer(int customerId)
{
const string sqlText = "SELECT Id, OrderDate, ... FROM Customers"
+ "WHERE CustomerId = @CustomerId";
List<Order> orders = new List<Order>();
using (var dbConnection = new SQLiteConnection(this.dbConnectionString))
{
using (var dbCommand = dbConnection.CreateCommand())
{
dbCommand.CommandText = sqlText;
dbCommand.Parameters.AddWithValue("@CustomerId", customerId);
dbConnection.Open();
using (SQLiteDataReader dbReader = await dbCommand.ExecuteReaderAsync())
{
while (await dbReader.ReadAsync())
{
var order = new Order
{
Id = dbReader.GetInt64(0),
...
};
orders.Add(forder);
}
}
}
}
return orders;
}
Most of the work is done by the Database management system. The only work that you do is copying the fetched data to Order and adding it to the List.
Although this can be optimized a little, if you use a method like this, your user interface will be quite responsive. For instance if you click a button to update the datagridview:
private async void ButtonUpdate_Clicked(object sender, ...)
{
this.buttonUpdate.Enabled = false;
this.ProgressBar.Value = this.ProgressBar.Minimum;
this.ProgressBar.Visible = true;
// Start the task to fetch the data, don't await for the result yet:
var taskFetchOrders = FetchOrdersOfCustomer(this.CustomerId);
// while the data is being fetched, which takes half a minute,
// show some progress.
while (!taskFetchOrders.Completed)
{
ProgressBar.PerformStep();
await Task.Delay(TimeSpan.FromSeconds(0.5));
}
List<Order> fetchedOrders = taskFetchOrders.Result;
this.UpdateDataGridView(fetchedOrders);
// clean up the mess:
this.progressBar.Visible = false;
this.buttonUpdate.Enabled = true;
}
Here you see that I don't await for the results of the task. I need to do something else: I need to update the ProgressBar. If your task is less than a few seconds, I wouldn't bother and just await taskFetchOrders
.
Because I await Task.Delay(...)
, my user interface is still responsive: it can react on other buttons, resizes, show dialog boxes, update progress bars etc. Every half second the Delay task is completed. It checks whether taskFetchOrders is completed, and if not it updates the progress bar and Delays again.
Hope this has given you some insight in how to use async-await in order to keep your application responsive.