2010-06-22 65 views
18

我喜欢在TPL的的Parallel.For和Parallel.ForEach扩展方法的简单性。我想知道是否有办法利用类似的东西,甚至是稍微更先进的任务。有没有办法在SQLDataReader中使用任务并行库(TPL)?

下面是SqlDataReader中的典型使用,我想知道,如果有可能,如果是如何在第三方物流的东西替换下面的while循环。由于读者无法提供固定次数的迭代,所以For扩展方法不可能处理我将收集的任务。我希望有人可能已经解决了这个问题,并用ADO.net解决了一些问题。

using (SqlConnection conn = new SqlConnection("myConnString")) 
using (SqlCommand comm = new SqlCommand("myQuery", conn)) 
{ 
    conn.Open(); 

    SqlDataReader reader = comm.ExecuteReader(); 

    if (reader.HasRows) 
    { 
     while (reader.Read()) 
     { 
      // Do something with Reader 
     } 
    } 
} 

回答

17

你几乎没有。总结你在一个函数贴有此标志的代码:

IEnumerable<IDataRecord> MyQuery() 

,然后用这个替换您的// Do something with Reader代码:

yield return reader; 

现在你有一些在单线程工作。不幸的是,当你通过查询读取结果这是一个参考,每次回到相同对象,该对象只是变异本身对每个迭代。这意味着如果你试图并行运行它,你会得到一些非常奇怪的结果,因为平行读取会改变不同线程中使用的对象。您需要使用代码将记录的副本发送到并行循环。

在这一点上,不过,我喜欢做的是跳过记录的额外拷贝,直接进入到一个强类型的类。更重要的是,我喜欢用一个通用的方法来做到这一点:

IEnumerable<T> GetData<T>(Func<IDataRecord, T> factory, string sql, Action<SqlParameterCollection> addParameters) 
{ 
    using (var cn = new SqlConnection("My connection string")) 
    using (var cmd = new SqlCommand(sql, cn)) 
    { 
     addParameters(cmd.Parameters); 

     cn.Open(); 
     using (var rdr = cmd.ExecuteReader()) 
     { 
      while (rdr.Read()) 
      { 
       yield return factory(rdr); 
      } 
     } 
    } 
} 

假设你的工厂方法创建如预期的副本,此代码应该是安全的Parallel.ForEach循环使用。调用该方法会是这个样子(假设一个Employee类名为静态工厂方法“创建”):

var UnderPaid = GetData<Employee>(Employee.Create, 
     "SELECT * FROM Employee WHERE AnnualSalary <= @MinSalary", 
     p => { 
      p.Add("@MinSalary", SqlDbType.Int).Value = 50000; 
     }); 
Parallel.ForEach(UnderPaid, e => e.GiveRaise()); 

重要更新:
我不是这个代码,我一样自信曾经是。当另一个线程正在进行复制时,一个单独的线程仍然可以改变读者。我可以对此加以锁定,但是我也担心另一个线程可能会在原始文件自己调用Read()之后但在开始复制之前调用更新阅读器。因此,这里的关键部分由整个while循环组成......在这一点上,你又回到了单线程。我期望有一种方法可以修改此代码,以便按照预期为多线程场景工作,但需要更多的研究。

+0

我跟你说你说的大部分内容,你在工厂上让我失去了一点点。当使用yeild return factor(rdr)时,Func 工厂不匹配调用我认为你的意思是Func 。所以不知道你的意思是按照预期复制。你的意思是基本上从读者那里读取并返回类似于里德在回复中所说的MyDataClass? – 2010-06-22 23:10:59

+0

也看起来像你的GetData调用是我们的命令你有工厂func之前的SQL字符串。无论我认为我得到了它,您的Employee.Create是您的工厂,能够为读者提供所需的工作。我会玩一会儿,看看它是如何发生的。 – 2010-06-22 23:25:02

+0

是的,我的意思是Func 。将解决这个问题和参数不匹配。 – 2010-06-22 23:43:04

21

您将无法直接替换while循环。 SqlDataReader不是 一个线程安全类,所以你不能直接从多个线程中使用它。

这就是说,你可能潜在进程您使用TPL读取的数据。这里有几个选项。最简单的做法可能是让你自己的IEnumerable<T>实现在读写器上工作,并返回一个包含你的数据的类或结构体。然后,您可以使用PLINQ或Parallel.ForEach语句来并行处理数据:

public IEnumerable<MyDataClass> ReadData() 
{ 
    using (SqlConnection conn = new SqlConnection("myConnString")) 
    using (SqlCommand comm = new SqlCommand("myQuery", conn)) 
    { 
     conn.Open(); 

     SqlDataReader reader = comm.ExecuteReader(); 

     if (reader.HasRows) 
     { 
      while (reader.Read()) 
      { 
       yield return new MyDataClass(... data from reader ...); 
      } 
     } 
    } 
} 

一旦你的方法,你可以直接处理这一点,通过PLINQ或TPL:

Parallel.ForEach(this.ReadData(), data => 
{ 
    // Use the data here... 
}); 

或者:

this.ReadData().AsParallel().ForAll(data => 
{ 
    // Use the data here... 
}); 
相关问题