简体   繁体   中英

Force a main Thread element to run in another created thread

I'm working on a C# winforms application. I try to execute a code that return a huge data from database (Using LINQ with Entity Framework ) on a new created Thread , and I show the data returned in DataGridView .

Code block :

        GVentesEntities context=new GVentesEntities();

        Thread t =new Thread(() =>
        {

            var R = context.Client.AsQueryable();

            Invoke(new Action(() =>
            {

                dataGridView1.DataSource = R.ToList();

            }));
        });

        t.Start();

The problem is after starting the Form the data load slowly in DataGridView , as I know that DataGridView runing on the main Thread .

How can I let DataGridView runing on another new Thread for fast loading data ? Or if you have any other solution I don't know about it. Massive thanks in advance.

You're executing the query back on the UI thread. Don't do that. Call ToList() outside of the Invoke , and then you're only passing the results to the grid. Of course, there may still be performance issues with that, but that's a rather broad topic of dynamic data loading.

Multi-threaded GUI is very tricky, and rarely worth the trouble. Try to separate business logic from GUI instead, and make sure the GUI doesn't do too much unnecessary stuff. In general, if you want to show a lot of data in a component, you need to use eg a virtual grid - instead of building all the grid cells when you assign data to the DataSource , you only create them on demand (when they become visible etc.). Unlike, say, Delphi, this doesn't happen automatically with .NET's DataGridView - you need to write a bit of code.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM