I know you can read a gui control from a worker thread without using Invoke/BeginInvoke because my app is doing it now. The cross thread exception error is not being thrown and my System.Timers.Timer thread is able to read gui control values just fine (unlike this guy: can a worker thread read a control in the GUI?)
Question 1: Given the cardinal rule of threads, should I be using Invoke/BeginInvoke to read form control values? And does this make it more thread-safe? The background to this question stems from a problem my app is having. It seems to randomly corrupt form controls another thread is referencing. (see question 2)
Question 2: I have a second thread that needs to update form control values so I Invoke/BeginInvoke to update those values. Well this same thread needs a reference to those controls so it can update them. It holds a list of these controls (say DataGridViewRow objects). Sometimes (not always), the DataGridViewRow reference gets "corrupt". What I mean by corrupt, is the reference is still valid, but some of the DataGridViewRow properties are null (ex: row.Cells). Is this caused by question 1 or can you give me any tips on why this might be happening?
Here's some code (the last line has the problem):
public partial class MyForm : Form
{
void Timer_Elapsed(object sender)
{
// we're on a new thread (this function gets called every few seconds)
UpdateUiHelper updateUiHelper = new UpdateUiHelper(this);
foreach (DataGridViewRow row in dataGridView1.Rows)
{
object[] values = GetValuesFromDb();
updateUiHelper.UpdateRowValues(row, values[0]);
}
// .. do other work here
updateUiHelper.UpdateUi();
}
}
public class UpdateUiHelper
{
private readonly Form _form;
private Dictionary<DataGridViewRow, object> _rows;
private delegate void RowDelegate(DataGridViewRow row);
private readonly object _lockObject = new object();
public UpdateUiHelper(Form form)
{
_form = form;
_rows = new Dictionary<DataGridViewRow, object>();
}
public void UpdateRowValues(DataGridViewRow row, object value)
{
if (_rows.ContainsKey(row))
_rows[row] = value;
else
{
lock (_lockObject)
{
_rows.Add(row, value);
}
}
}
public void UpdateUi()
{
foreach (DataGridViewRow row in _rows.Keys)
{
SetRowValueThreadSafe(row);
}
}
private void SetRowValueThreadSafe(DataGridViewRow row)
{
if (_form.InvokeRequired)
{
_form.Invoke(new RowDelegate(SetRowValueThreadSafe), new object[] { row });
return;
}
// now we're on the UI thread
object newValue = _rows[row];
row.Cells[0].Value = newValue; // randomly errors here with NullReferenceException, but row is never null!
}