如果不使用来自另一个线程的Invoke / BeginInvoke,读取表单控件值(但不能更改它)是否是线程安全的

时间:2012-09-06 21:13:41

标签: .net multithreading .net-3.5

我知道您可以在不使用Invoke / BeginInvoke的情况下从工作线程读取gui控件,因为我的应用程序现在正在执行此操作。不会抛出跨线程异常错误,我的System.Timers.Timer线程能够很好地读取gui控件值(不像这个人:can a worker thread read a control in the GUI?

问题1:鉴于线程的基本规则,我应该使用Invoke / BeginInvoke来读取表单控件值吗?这是否使它更安全?这个问题的背景源于我的应用程序遇到的问题。它似乎随机损坏另一个线程引用的表单控件。 (见问题2)

问题2:我有第二个线程需要更新表单控件值,所以我调用/ BeginInvoke来更新这些值。那么这个相同的线程需要对这些控件的引用,以便它可以更新它们。它包含这些控件的列表(比如DataGridViewRow对象)。有时(并非总是),DataGridViewRow引用变得“损坏”。我的意思是腐败,是引用仍然有效,但一些DataGridViewRow属性为null(例如:row.Cells)。这是由问题1引起的还是你可以给我任何关于为什么会发生这种情况的提示?

这是一些代码(最后一行有问题):

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);       

        // Is it thread-safe to step through the datagrid rows here without invoking?
        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!
    }

1 个答案:

答案 0 :(得分:6)

RE 1:基本规则是必须在运行其消息泵的线程上访问Windows控件。在.NET中,这是运行Application.Run的线程。它是处理消息的人,你对Windows控件所做的一切都是一条消息。因此,如果您从另一个线程发送消息,处理消息的线程可能具有竞争条件(消息泵不是线程安全的,因为这会对性能造成太大影响,以及其他原因)。现在,访问.NET类实例不一定会导致发送消息。在这些情况下,是的,可以从不是UI线程的线程访问它们。什么做和不发送消息或在什么情况下单个成员做什么和不发送消息没有记录。因此,您只是赌博,当您从非UI线程访问控制对象时,不会发生异常。最佳做法是始终使用InvokeRequired / BeginInvoke

,当你最不期望的时候,任何其他事情都可能改变,如果它失败了,你所做的事情就不会受到支持。

RE 2:我不能告诉你1是否引起2 - 真的没有足够的细节。如果你以不受支持的方式做某事,那么预计会发生意想不到的事情是合理的。

如果这是一个WinForms应用程序,我建议改为使用Forms.Timer。它调用UI线程上的Tick处理程序,您不必费心InvokeRequired / BeginInvoke