H W
H W

Reputation: 2586

Async loading for winforms

In (Android) Apps it is quite common to have ListViews loading and being built while the user is interacting with the interface. In Winforms however the tendency seems to be clicking a button and waiting for the result to be fully loaded before the user can continue navigating through the application.

Since the database access that i am currently using is quite slow, i would like to use the database in async methods to enable the user to keep interacting with the interface, while the data is not fully loaded and displayed.

For example i would like to start an asynchronous method in my Form_Load event to keep gathering data. When this method completes, i want to bind the data to some controls - this will (for now) not change the functionality at all. Thus i want the user not to notice any difference (except for the data being shown or not) when handling the application.

Where am i supposed to place the await keyword to accomplish this? I can't place it in my Load event since this needs to complete for the application to behave "normally".

Is it even possible with async methods to have windows forms being fully reactional while not all methods completed, or am i looking at the 'wrong' functionality for my purposes?

Thank you in advance.

Edit: Following Srirams hint, I made the load-event itself an async sub which worked out well. Here is some simple sample code which shows the desired behaviour:

Public Class DelayedLoadingWindow

    Private Async Sub DelayedLoadingWindow_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim awaitedResultOne As Task(Of String) = GetDataOneAsync()
        Label1.Text = Await awaitedResultOne
        Dim awaitedResultTwo As Task(Of String) = GetDataTwoAsync()
        Label2.Text = Await GetDataTwoAsync()
        Dim awaitedResultThree As Task(Of String) = GetDataThreeAsync()
        Label3.Text = Await GetDataThreeAsync()
        Me.Text = "DONE"
    End Sub

    Public Async Function GetDataOneAsync() As Task(Of String)
        Await Task.Delay(2000)
        Return "Async Methods"
    End Function

    Public Async Function GetDataTwoAsync() As Task(Of String)
        Await Task.Delay(2000)
        Return "are"
    End Function

    Public Async Function GetDataThreeAsync() As Task(Of String)
        Await Task.Delay(2000)
        Return "running!"
    End Function
End Class

Upvotes: 8

Views: 22974

Answers (3)

Frank Thompson
Frank Thompson

Reputation: 1

I liked the concept of using a backround worker but there is a limitation that any behavior executed in background thread can not update the user interface without using a Dispatcher.

So within the button-click handler, I addressed as follows:

        /// <summary>
        /// Handles Button click event to populate a data grid async
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnSubmit_Click(object sender, EventArgs e)
        {
            btnSubmit.Enabled = false;
            dataGridView1.Visible = false;
            lbStatus.Text = "Getting Search History ...";
            var worker = new BackgroundWorker();
            worker.DoWork += Worker_DoWork;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            worker.RunWorkerAsync();
        }

The next task is to fetch data from the database and store it's content in the List object which is bound to the DataGridView control.

        /// <summary>
        /// Fetch data from remote data source on store in IList
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            _vwlist.Clear();
            _vwlist.AddRange(GetHistory());
            e.Result = true;
        }

Upon completion handle the user interface interactions.


        /// <summary>
        /// Handles data-binding and user interface state after data completion.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            dataGridView1.DataSource = _vwlist;
            btnSubmit.Enabled = true;
            dataGridView1.Visible = true;
            lbStatus.Text = "Ready";
        }

Upvotes: 0

Vaibhav J
Vaibhav J

Reputation: 1334

You can use the BackgroundWorker Control,

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx

You will need to just put the BackgroundWorker Control on your form (backgroundWorker1 in our case), BackgroundWorker Control also supports cancellation, RunWorkerCompleted etc.

private SlowLoadingForm frm;

private void startAsyncButton_Click(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    frm = new SlowLoadingForm();
    frm.show();
    Application.Run(frm);
}

Upvotes: -2

Sriram Sakthivel
Sriram Sakthivel

Reputation: 73482

Where am i supposed to place the await keyword to accomplish this? I can't place it in my Load event since this needs to complete for the application to behave "normally".

Why can't you await inside load event handler? You can do that if you mark the method with async modifier.

private async void Form_Load(object sender, EventArgs e)
{
    //Do something
    var data = await GetDataFromDatabaseAsync();
    //Use data to load the UI
}

This way, you can keep the UI responsive and also execute the time consuming work asynchronously. GetDataFromDatabaseAsync has to be asynchronous (should not block the calling thread).

If this doesn't answers your question, please be more specific.

Upvotes: 17

Related Questions