从不同线程同时从 DataSet 添加和删除行

Adding and deleting rows from DataSet concurrently from different threads

我以为我知道 lock 语句是如何工作的,但似乎我不知道...我正在尝试实现使我能够添加和删除行的功能 to/from DataSet 同时从单独的线程(WinForms 项目)。因此,一个线程每隔一段时间向 DataSet 添加新行,而另一个线程从同一 DataSet 中删除行。我假设 DataSet 总是有一些行要删除 - 我想在这里关注多线程问题。将行添加到 DataSet 的线程比删除行的线程运行得更快。为了创建新线程,我使用 System.Threading.Timer class 来启动新线程,同时在此 class 上创建新实例。我不明白为什么我的代码中有 lock 语句时仍然得到 System.InvalidOperationException。进一步的消息是:"Additional information: Cross thread operation detected."

我应该如何使用 lock 语句? 我究竟应该在我的代码中的什么地方放置 lock 语句?

代码:

using System;
using System.Data;
using System.Threading;
using System.Windows.Forms;

namespace TEST_WinForms
{
public partial class Form1 : Form
{
    private const String _strDSname = "dataSet";
    private const String _strTableName = "dtBuffer";

    private object _objLockDataSet = new object();

    public Form1()
    {
        InitializeComponent();
    }

    private void BuildDataSet()
    {
        DS = new DataSet();
        DS.Tables.Add(_strTableName);

        foreach (DataTable table in DS.Tables)
        {
            table.Columns.Add("LP", typeof(Int32));
            table.Columns.Add("Date and time", typeof(DateTime));
        }
    }

    private void StartT1timer()
    {
        try
        {
            if (T1 == null)
            {
                T1 = new System.Threading.Timer(new TimerCallback(AddRow)
                                              , null
                                              , 0
                                              , Timeout.Infinite);
            }
            else
            {
                T1.Change(0, Timeout.Infinite);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private void StartT2timer()
    {
        try
        {
            if (T2 == null)
            {
                T2 = new System.Threading.Timer(new TimerCallback(DeleteRow)
                                              , null
                                              , 1000
                                              , Timeout.Infinite);
            }
            else
            {
                T2.Change(1000, Timeout.Infinite);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private void AddRow(object state)
    {
        lock (_objLockDataSet)
        {
            try
            {
                this.Invoke(new MethodInvoker(() =>
                {
                    DataRow newRow = DS.Tables[_strTableName].NewRow();

                    if (DS.Tables[_strTableName].Rows.Count > 0)
                    {
                        newRow["LP"] =
                            (int)DS.Tables[_strTableName].Rows[DS.Tables[_strTableName].Rows.Count - 1]["LP"] + 1;
                    }
                    else
                    {
                        newRow["LP"] = 1;
                    }

                    newRow["Date and time"] = DateTime.Now;

                    DS.Tables[_strTableName].Rows.Add(newRow);

                }));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        T1.Change(100, Timeout.Infinite);
    }

    private void DeleteRow(object state)
    {
        try
        {
            lock (_objLockDataSet)
            {
                if (DS.Tables[_strTableName].Rows.Count > 0)
                {
                    DS.Tables[_strTableName].Rows.RemoveAt(0);
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }

        T2.Change(500, Timeout.Infinite);
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        BuildDataSet();

        gridControl1.DataSource = DS.Tables[_strTableName];

        StartT1timer();

        StartT2timer();
    }

    private DataSet DS
    {
        get;
        set;
    }

    private System.Threading.Timer T1
    {
        get;
        set;
    }

    private System.Threading.Timer T2
    {
        get;
        set;
    }
}
}

问题不在于 lock 语句,而在于从非 UI 线程的线程访问 UI 控件(从数据源中删除行)。

实际上你在添加行时做对了,所以在删除时做同样的事情 - 使用表单的 Invoke 方法以便在正确的线程上执行委托。