可以并行处理非线程安全的代码吗?

时间:2016-03-10 14:22:11

标签: c# multithreading parallel-processing

我有一个C#程序在foreach上执行DataRows。简化版本是:

foreach (DataRow row in masterReportData.Rows)
{
    LoopThruReports(row, p2, p3, p4);
}

LoopThruReports被声明为:

public static void LoopThruReports(DataRow row, DataTable p2, string p3, string p4)

我真的希望让迭代并行运行,因为它们从数据库中填充SqlDataAdapter(需要一段时间)。尽管尝试了线程,任务和并行库,但我无法弄清楚这是否可行。如果只有我可以让每个线程不与其他线程共享变量!有没有人对这是否可能有任何想法,如果是的话,我怎么能这样做?感谢。

编辑:LoopThruReports方法调用GetGCHAReportDetailData,这是Fill完成的地方。这是这个方法的样子:

    public static DataSet GetGCHAReportDetailData(int reinsuranceContract, int billingMode, DateTime reportPeriod)
    {
        DataSet dsResults = new DataSet();
        DataTable gchaReportData = new DataTable();

        string connStr = ConfigurationManager.ConnectionStrings["UtopiaConnString"].ConnectionString;

        using (SqlCommand sqlCmd = new SqlCommand("dbo.[sproc_GCHADetailDataForContract]"))
        {
            sqlCmd.CommandType = CommandType.StoredProcedure;

            //set the command time out to 60 minutes b/c this report takes time to generate
            sqlCmd.CommandTimeout = 0;

            SqlParameter parm = new SqlParameter("@ReinsuranceContractId", SqlDbType.Int);
            parm.Value = reinsuranceContract;
            sqlCmd.Parameters.Add(parm);

            SqlParameter parm1 = new SqlParameter("@BillingMode", SqlDbType.Int);
            parm1.Value = billingMode;
            sqlCmd.Parameters.Add(parm1);

            SqlParameter parm2 = new SqlParameter("@ReportingPeriod", SqlDbType.DateTime);
            parm2.Value = reportPeriod;
            sqlCmd.Parameters.Add(parm2);
            sqlCmd.CommandTimeout = 0;

            using (SqlConnection conn = new SqlConnection(connStr))
            {
                sqlCmd.Connection = conn;

                using (SqlDataAdapter da = new SqlDataAdapter(sqlCmd))
                {
                    try
                    {
                        conn.Open();
                        da.Fill(dsResults);

                        conn.Close();
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex, "Error retrieving data from the database.\r\n Message: {0}", ex.Message);
                        throw;
                    }
                    finally
                    {
                        if (conn.State != ConnectionState.Closed)
                            conn.Close();
                    }
                }
            }
        }

        return dsResults;
    }

2 个答案:

答案 0 :(得分:1)

尝试多线程多个Fill调用同一个SQL服务器将是一场失败的战斗。 SQL服务器和网络都已经尽可能快地向您发送数据。通过在多个线程上分割Fill次调用,实际上会减慢速度。带宽将在每个线程之间平均分配(因此不会增加性能),除了每个线程也会产生一些开销,这会使事情变慢一些。您添加的每个线程都会更加复杂化这个问题。

如果每个Fill针对不同的数据库运行,那么可能能够获得一些线程改进,因为他们不会竞争相同的SQL资源,尽管网络争论仍然是一个问题。

我在代码中看不到任何会导致多线程不安全的内容,我只是不知道如何通过线程来改善处理时间。

注释表明您实际上是从数据生成报告,这就是您想要多线程的。这实际上可行。只要报表只读取数据,从不写数据,您就应该能够对它们进行线程化,并共享单个数据集。

根据MSDNDataTablesDataSet有此注释"此类型对于多线程读取操作是安全的。您必须同步任何写操作。"

在线程之间共享相同的DataSet实例应该完全没问题,只要您读取编写就需要特殊处理。< / p>

答案 1 :(得分:0)

此代码似乎有效:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ParallelDBProcessing
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Task> taskList = new List<Task>();
            for (int i = 1; i <= 4; i++)
            {
                int temp = i;
                Task task = Task.Run(() => DoWork(temp));   // use Task.Run if you want to get task.Result back
                taskList.Add(task);
            }
            Task.WaitAll(taskList.ToArray());
        }

        public static void DoWork(int num)
        {
            Console.WriteLine(num);
            string connstr = @"Data Source = (local)\sqlexpress; Initial Catalog = Barry; Integrated Security = true";

            DataSet dsResults = new DataSet();
            using (SqlCommand sqlCmd = new SqlCommand("dbo.[spMagicProc]"))
            {
                sqlCmd.CommandType = CommandType.StoredProcedure;

                SqlParameter parm = new SqlParameter("@Input_A", SqlDbType.Int);
                parm.Value = num;
                sqlCmd.Parameters.Add(parm);

                SqlParameter parm1 = new SqlParameter("@Input_B", SqlDbType.Int);
                parm1.Value = 2;
                sqlCmd.Parameters.Add(parm1);

                using (SqlConnection conn = new SqlConnection(connstr))
                {
                    sqlCmd.Connection = conn;

                    using (SqlDataAdapter da = new SqlDataAdapter(sqlCmd))
                    {
                        try
                        {
                            conn.Open();
                            da.Fill(dsResults);
                            conn.Close();
                        }
                        catch (Exception ex)
                        {
                            throw;
                        }
                        finally
                        {
                            if (conn.State != ConnectionState.Closed)
                                conn.Close();
                        }
                    }

                }
                Console.WriteLine(dsResults.Tables[0].Rows.Count);
            }
        }
    }
}