Parallel.ForEach和阻塞线程

时间:2015-01-14 20:28:51

标签: c# multithreading parallel-processing task-parallel-library

我使用Quartz.NET库创建了Windows Service应用程序,以便为报告目的安排作业。应用程序的主要部分是从不同位置的数据库中获取一些数据(~260),因此我决定使用Parallel.ForEach在中心位置并行获取和存储数据。

在Quartz.NET Job中,我从我的实用程序类中运行静态方法,进行并行处理。

公用事业类:

public class Helper
{
    public static ConcurrentQueue<Exception> KolekcijaGresaka = new ConcurrentQueue<Exception>();   // Thread-safe

    public static void Start()
    {
        List<KeyValuePair<string, string>> podaci = Aktivne();  // List of data for processing (260 items)
        ParallelOptions opcije = new ParallelOptions { MaxDegreeOfParallelism = 50 };
        Parallel.ForEach(podaci, opcije, p =>
        {
            UzmiPodatke(p.Key, p.Value, 2000);
        });
    }

    public static void UzmiPodatke(string oznaka, string ipAdresa, int pingTimeout)
    {
        string datumTrenutneString = DateTime.Now.ToString("d.M.yyyy");
        string datumPrethodneString = DatumPrethodneGodineString();
        string sati = DateTime.Now.ToString("HH");

        // Ping:
        Ping ping = new Ping();
        PingReply reply = ping.Send(ipAdresa, pingTimeout);

        // If is online call method for copy data:
        if (reply.Status == IPStatus.Success)
        {
            KopirajPodatke(oznaka, ipAdresa, datumTrenutneString, datumPrethodneString, sati, "TBL_DATA");
        }
    }
    public static void KopirajPodatke(string oznaka, string ipAdresa, string datumTrenutneString, string datumPrethodneString, string sati, string tabelaDestinacija)
    {
        string lanString = "Database=" + ipAdresa + "://DBS//custdb.gdb; User=*******; Password=*******; Dialect=3;";
        IDbConnection lanKonekcija = new FbConnection(lanString);
        IDbCommand lanCmd = lanKonekcija.CreateCommand();

        try
        {
            lanKonekcija.Open();
            lanCmd.CommandText = "query ...";
            DataTable podaciTabela = new DataTable();

            // Get data from remote location:
            try
            {
                podaciTabela.Load(lanCmd.ExecuteReader());
            }
            catch (Exception ex)
            {
                throw ex;
            }

            // Save data:
            if (podaciTabela.Rows.Count > 0)
            {
                using (SqlConnection sqlKonekcija = new SqlConnection(Konekcije.DB("Podaci")))
                {
                    sqlKonekcija.Open();
                    using (SqlBulkCopy bulkcopy = new SqlBulkCopy(sqlKonekcija))
                    {
                        bulkcopy.DestinationTableName = tabelaDestinacija;
                        bulkcopy.BulkCopyTimeout = 5;  // seconds
                        bulkcopy.ColumnMappings.Add("A", "A");
                        bulkcopy.ColumnMappings.Add("B", "B");
                        bulkcopy.ColumnMappings.Add("C", "C");
                        bulkcopy.ColumnMappings.Add("D", "D");
                        try
                        {
                            bulkcopy.WriteToServer(podaciTabela);
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            KolekcijaGresaka.Enqueue(ex);
        }
        finally
        {
            lanCmd.Dispose();
            lanKonekcija.Close();
            lanKonekcija.Dispose();
        }
    }

应用程序大部分时间都在工作(作业每天执行4次),但有时会卡住并挂起(通常在处理~200项并行时),从而阻塞主线程并永不停止。 似乎来自并行处理的线程之一被阻止并阻止主线程的执行。这可能是死锁引起的吗?

如何确保没有一个线程阻止应用程序执行(即使没有成功获取数据)?上面的代码有什么问题?

1 个答案:

答案 0 :(得分:0)

  

如何确保没有一个线程阻止应用程序执行(即使没有成功获取数据)?上面的代码有什么问题?

Parallel.Foreach不是异步的,它只是并行执行每次迭代,所以它会在继续之前等待每个操作完成。如果你真的不想在返回调用者之前等待所有操作完成,那么尝试使用Task工厂来安排这些操作并默认使用线程池。

foreach(var p in podaci)
{
    Task.Factory.StartNew(() => UzmiPodatke(p.Key, p.Value, 2000));
}

或者使用ThreadPool.QueueUserWorkItem或BackgroundWorker,无论您熟悉什么,并且适用于您想要的行为。

这可能无法解决您的所有问题,只是无法响应的程序。最有可能的是,如果您的代码确实存在问题,那么您的一个任务最终会抛出一个异常,如果未处理则会导致程序崩溃。或者更糟糕的是,如果任务永远不会完成,那么你只会坐在那里占用资源。然而,可能只是偶尔其中一个需要非常长的情况。在这种情况下,您可以根据需要处理此问题(取消长任务,确保所有先前计划的任务在安排更多之前完成,等等),并且任务并行库可以通过一些小的修改来支持所有这些情况。