如何确保一次只有一个线程可以访问DataTable

时间:2014-05-12 07:29:07

标签: c# .net console-application

我有这个Windows控制台应用程序:

using System;
using System.Text;

namespace ThreadLockSample
{
    class Program
    {
        static void P1(System.Data.DataTable dt)
        {

            for (int i = 0; i < 100000; i++)
            {
                dt.Rows.Add(i);
            }
        }
        static void P2(System.Data.DataTable dt)
        {

            for (int i = 100000; i < 200000; i++)
            {
                dt.Rows.Add(i);
            }
        }

        static void Main(string[] args)
        {
            System.Data.DataTable dt = new System.Data.DataTable();
            for (int i = 0; i < 200; i++)
                dt.Columns.Add("Id " + i);

            System.Threading.Thread t1 = new System.Threading.Thread(() => P1(dt));
            System.Threading.Thread t2 = new System.Threading.Thread(() => P2(dt));
            t1.Start();
            t2.Start();
            t1.Join();
            t2.Join();
        }
    }
}

现在我传递的DataTable可以访问两个线程,但应用程序冻结了 - 如何更改我的代码以解决此问题。

2 个答案:

答案 0 :(得分:6)

运行此应用程序时,您不会遇到死锁。相反,您会收到一条错误,指出某些数据在DataTable内已损坏,因为DataTable本身有一些内部的并发检查。

我认为这个应用程序是真实应用程序的简单版本。您可以采取以下几个步骤来改进您的计划:

  • 避免使用共享数据进行多线程处理

使用共享数据进行多线程处理很困难。您是否可以简化您的计划,以便您不需要任何共享数据?也许你可以并行运行两个操作,返回一个结果,然后合并这些结果。这样可以避免许多潜在的问题。

如果不可能,您可以执行以下操作:

  • 添加锁

您可以像这样创建一个锁定对象:

private static object _lock = new object();

现在,您可以使用以下命令对数据表进行调用:

lock (_lock)
{
    dt.Rows.Add(i);
}
  • 不要直接使用Threads。而是使用任务并行库:

Task对象是围绕Thread的智能包装器。建议使用任务。

Task t1 = Task.Run(() => P1(dt));
Task t2 = Task.Run(() => P2(dt));

Task.WaitAll(t1, t2);

更新

由于最初的问题现已从死锁变为冻结,因此还需要考虑其他一些事项。

在控制台应用程序中,您将始终需要等待,直到完成两项任务。等待将冻结您的UI。当然,你可以使用像计时器这样的聪明的东西来检查任务是否已经完成,或者除了控制台应用程序之外的某些东西确实不适用于这样的情况。

但是,如果您的应用程序实际上是一个WinForms或WPF应用程序,您应该考虑使用异步代码和C#5中添加的新async / await关键字。它们与Tasks完美配合,它们允许您在另一个线程上运行代码UI保持响应。任务完成后,结果将合并回UI线程,用户将保留响应式应用程序。

有关async / await的更多信息,您可以从这里开始:Asynchronous Programming with Async and Await (C# and Visual Basic)

答案 1 :(得分:1)

除了Wouter de Kort的回答:

如果要在DataTable上使用锁定,请使用DataTable.Rows.SyncRoot对象,如

lock(dt.Rows.SyncRoot)
    //Your code here

因为它是假定的锁定对象。