Reputation: 335
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
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);
Upvotes: 0
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
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