简体   繁体   English

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

[英]Possible to process non-thread-safe code in parallel?

I have a C# program that performs a foreach on DataRows . 我有一个在DataRows上执行foreach的C#程序。 The simplified version is: 简化版为:

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

And LoopThruReports is declared as: LoopThruReports声明为:

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

I'd really like to have the iterations run in parallel because they fill a SqlDataAdapter from a database (which takes a while). 我真的很想让迭代并行运行,因为它们从数据库中填充了SqlDataAdapter (这需要一段时间)。 Despite trying threads, tasks, and the parallel library, I cannot figure out if this is even possible to do. 尽管尝试了线程,任务和并行库,但我什至无法确定这样做是否可行。 If only I could have each thread not share variables with other threads! 如果只能让每个线程不与其他线程共享变量! Does anyone have any ideas on whether this is possible, and if so, how I could do it? 是否有人对这是否可能有任何想法,如果可以,我该怎么做? Thanks. 谢谢。

EDIT: The LoopThruReports method calls GetGCHAReportDetailData , which is where the Fill is done. 编辑: LoopThruReports方法调用GetGCHAReportDetailData ,这是Fill地方。 Here's what this method looks like: 这是这种方法的样子:

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

Trying to multi-thread multiple Fill calls to the same SQL server will be a loosing battle. 尝试对同一SQL Server的多个Fill调用进行多线程将是一场艰苦的战斗。 Both he SQL server and the network are already sending you data as fast as they can. SQL Server和网络都已经尽可能快地向您发送数据。 By splitting the Fill calls over multiple threads you are actually going to slow things down. 通过将Fill调用划分为多个线程,您实际上会减慢速度。 The bandwidth will be split equally between each thread (so there will be no performance increase), except each thread will also incur some overhead, which will slow things down a bit. 带宽将在每个线程之间平均分配(因此不会提高性能),除了每个线程也会增加一些开销,这会降低速度。 Each thread you add will compound this problem more. 您添加的每个线程都会使这个问题更加复杂。

If each Fill runs against a different database then you might be able to get some threading improvement, as they won't be competing for the same SQL resources, though network contention will still be an issue. 如果每个Fill在不同的数据库上运行,那么您可能会在线程方面有所改进,因为尽管网络争用仍然是一个问题,但它们不会竞争相同的SQL资源。

I don't see anything in your code that would make multi-threading unsafe, I just don't see how threading it will improve your processing time. 我在您的代码中看不到任何会使多线程不安全的东西,只是看不到对其进行线程处理将如何改善您的处理时间。


Comments indicate that you are actually generating reports from the data, and that is what you want to multi-thread. 注释表明您实际上是根据数据生成报告,并且这就是您要进行多线程处理的内容。 This may actually work. 这可能实际上可行。 As long as the reports only read data, and never write data, you should be able to thread them, and share a single dataset. 只要报告仅读取数据,而不写入数据,您就应该能够对它们进行线程化,并共享单个数据集。

According to MSDN , DataTables and DataSet have this note "This type is safe for multithreaded read operations. You must synchronize any write operations." 根据MSDN的说明DataTablesDataSet具有此注释“此类型对于多线程读取操作是安全的。您必须同步任何写入操作。”

It should be perfectly fine to share the same DataSet instance between threads as long as you only read from it, writing will require special handling. 它应该是完全正常的共享同一个DataSet只要线程之间例如因为你只从它都需要进行特殊处理。

This code seems to work: 这段代码似乎有效:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM