mickG
mickG

Reputation: 335

Update C# datagridviews in separate thread

I have a windows form with a datagridview and some buttons. One of the buttons when clicked will call a method called loadMyData() that reads some data from a csv and puts them in three datagridviews in the form.

The code is something like this:

public partial class NewForm : Form
{

    private void loadData_Click_1(object sender, EventArgs e) // load market data, create a base copy and update gridview
    {
        ThreadStart thread1Start = new ThreadStart(loadMyData);
        Thread t1 = new Thread(thread1Start);
        t1.Start();          
    }

    public void loadMyData()
    {
    dataMap = dataLoader.newLoadTheData(dataMap, grid1, grid2) 


    }
}

where dataLoader.newLoadTheData is a static method that takes as input my datagridviews (grid1, grid2) and a dictionary (dataMap). The method simply reads some data from a csv and put the numbers in the 2 datagridviews. These are updated from this method and an updated dictionary (dataMap) is also returned by the method. It all works fine when the method loadMyData() is executed normally but I get this error when I execute it as thread:

Cross-thread operation not valid: Control 'grid1' accessed from a thread other than the thread it was created on.

I realize that I might be using something like "invoke" but I really can't find a clear example that shows how to do this in my case. Can anyone help with tjis situation? How should I change the code to make it work?

Upvotes: 1

Views: 6281

Answers (3)

user585968
user585968

Reputation:

System.Threading.Tasks allows you to easily create a child task and run a completion block when all is complete. If you specify the UI context then the completion block will run in the UI thread, no need for Invoke().

Code:

        var ui = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory.StartNew(
                              () =>
                              {
                                  // this runs in worker thread
                                  loadMyData(); 
                                  DoSomeLengthyJob();
                                  DoSomethingElse();
                              }) 
            .ContinueWith(t =>
                          {
                              // now we are in UI thread
                              // now update the UI with whatever you want
                              // with the results from your worker thread

                              dataGridView1.Rows.Add();
                          }, ui);

Tell me more

Upvotes: 0

Michal Ciechan
Michal Ciechan

Reputation: 13888

You have to marshal the call back to the UI thread.

Are you using WinForms or WPF?

In WPF you can use the Dispatcher.

In WinForms:

Try

// Get the UI thread's context in the constructor.
var context = TaskScheduler.FromCurrentSynchronizationContext();


        // Then its possible to start a task directly on the UI thread
        var token = Task.Factory.CancellationToken;
        Task.Factory.StartNew(() =>
        {
            this.label1.Text = "Task past first work section...";
        }, token, TaskCreationOptions.None, context);

EDIT:

The reason you are getting this error is because you are trying to access the Grid control from another thread. In general, most UI applications are in a STA(Single Threaded Affinity) model, where any interactions with the UI must be done on the "UI" thread which is usually the main/first thread the application starts on.

As you are loading the data on a background thread, after it is finished, you need a way to Marshal(invoke/run) the code which update the Grid on the Main/UI thread.

To achieve this, you create a TaskScheduler on the main thread by using its current SynchronizationContext (as in the constructor of the window/control the current context will be the UI thread) and than later you can pass that context into the Task.Factory.StartNew method as a parameter, so that it knows to "Marshal"(Invoke/Run) the code on the given "Context" which is the UI Thread

Upvotes: 0

Mihai Caracostea
Mihai Caracostea

Reputation: 8466

When working with your grid from the other thread, you should do something like this:

if (grid1.InvokeRequired)
   grid1.Invoke(new Action(() => { /*do my stuff here*/ })
else
{
   /*do my stuff here*/
}

Upvotes: 3

Related Questions