Ajay Sharma
Ajay Sharma

Reputation: 31

C# Windows application show picture box until background process finish

I have to call my API and show waiting PictureBox(wait.gif) until API get complete and return the data to bind my custom control into tablepanellayout.

I can't use BackgroundWorker class here because it has cross-thread issue with my sub control in custom control.

Here I have only idea to do that, is call child thread from main thread but until its get executed completely show the PictureBox(wait.gif) and block the rest code to be executed.

Can anybody suggest me how to do it exactly or please provide some code snippet for example.

Upvotes: 0

Views: 288

Answers (1)

Harald Coppoolse
Harald Coppoolse

Reputation: 30474

There are two articles that helped me a lot understanding async-await:

For WinForms you do the following:

  • If your data query function has async versions to fetch the data, use that function. Think of functions like SqlConnection.OpenAsync, Dapper.QueryAsync etc
  • If your data querier has no async versions make it async using Task.Run
  • Every function that calls async functions should be declared async itself
  • Every async function returns Task instead of void and Task<TResult> instead of TResult.
  • The only exception is the event handler: this function returns void instead of Task
  • the calls to other async functions should be awaited before your async returns.

.

// The event handler: make async. The only one that still returns void
async void OnButton1_clicked(object sender, ...)
{
    // start fetching the data. Don't await yet, you'll have other things to do
    Task<MyData> fetchDataTask = FetchData(...);

    // meanwhile: show the user that you are busy:
    this.ShowBusy(true); // show picture box?

    // if needed do other things you can do before the data is fetched
    this.ClearTable();

    // once you have nothing meaningful to do, await for your data
    MyData fetchedData = await fetchDataTask;
    this.ProcessData(fetchedData);

    // finished: 
    this.ShowBusy(false); // remove picture box
}

Async version of the function that fetched the data:

async Task<IQueryable<MyData>> FetchDataAsync(myParams)
{
    using (SqlConnection dbConnection = new SqlConnection(...)
    {
        // open the connection, don't wait yet:
        Task taskOpen = sqlCommand.OpenAsync();

        // continue while opening:
        using (var sqlCommand = new SqlCommand(...))
        {
            cmd.Parameters.AddWithValue(...);

            // before executing the query: wait until OpenAsync finished:
            await taskOpen;

            // read the data. If nothing to do: await, otherwise use Task similar to Open
            SqlDataReader dataReader = await cmd.ExecuteReaderAsync();
            foreach (var row in dataReader)
            { 
                ... (some Await with GetFieldValueAsync
            }
        }
    }
}

I'm not really familiar with reading SQL data on such a low level, I prefer entity framework and dapper, so there might be an error in my SqlReader stuff. Maybe someone can correct this. Still you'll get the gist.

If you write it like this, your program will be pretty responsive: Whenever the procedure has to await for something, control is given back to the caller of the function who can continue processing until he meets an await, when control is given back to the caller, who continues processing, etc.

Note that this won't help you if your program is not waiting for something. If your main thread does some heavy calculations for several seconds, your program won't be responsive. Consider creating an async function that will do this for your using Task.Run

If you have programmed like this, all threads performing your functions will have the same context: it will be as if only the UI-thread is involved. No need for mutexes, semaphores, InvokeRequired etc.

Upvotes: 3

Related Questions