将 class 添加到列表 C# 时,索引超出数组范围

Index was outside the bounds of the array when adding class to list C#

异常:索引超出数组范围。

首先,我很熟悉这个异常并且我之前已经修复它但是我在我的代码中的一个非常奇怪的行得到这个异常。当我将创建的用户 class 添加到代码中的 classes 列表时,它被抛出。我完全不知道它为什么抛出这个异常以及如何修复它。

public static async Task getData()
    {
        // initialize everything
        List<StockData> stockData = new List<StockData>();
        List<StockMarketCompare> stockCompareData = new List<StockMarketCompare>();
        List<StockData> sandpInfo = new List<StockData>();
        List<StockData> sandpDateInfo = new List<StockData>();
        List<StockData> amexList = new List<StockData>();
        List<DateTime> completedDates = new List<DateTime>();
        SymbolInfo symbolClass = new SymbolInfo();
        List<SymbolInfo> ratingSymbols = new List<SymbolInfo>();
        List<StockRating> ratingList = new List<StockRating>();
        bool isGoodToGo = false;
        string symbol, market;
        int activeSymbolsCount = 0;
        int rowCount = 0, completedRowCount = 0;
        DateTime date = new DateTime();
        DateTime searchDate = new DateTime();

        using (SqlConnection connection = new SqlConnection("connectionstring"))
        using (SqlCommand sandpCommand = new SqlCommand("select * from dbo.DailyGlobalData where Symbol='" + Calculations.sp500 + "'", connection))
        using (SqlDataAdapter sandpAdapter = new SqlDataAdapter(sandpCommand))
        using (DataTable sandpTable = new DataTable("sandp"))
        using (SqlCommand stockRatingsCommand = new SqlCommand("select * from dbo.StockRatings", connection))
        using (SqlDataAdapter stockRatingsAdapter = new SqlDataAdapter(stockRatingsCommand))
        using (DataTable stockRatingsTable = new DataTable("stockratings"))
        {
            try
            {
                // fill the sandptable
                sandpAdapter.Fill(sandpTable);

                if (sandpTable != null)
                {
                    var sandpQuery = from c in sandpTable.AsEnumerable()
                                     select new StockData { Close = c.Field<decimal>("Close"), Date = c.Field<DateTime>("Date"), High = c.Field<decimal>("High"), Low = c.Field<decimal>("Low"), Volume = c.Field<Int64>("Volume") };
                    sandpInfo = sandpQuery.AsParallel().ToList();
                }

                // fill the stockratingstable
                stockRatingsAdapter.Fill(stockRatingsTable);

                if (stockRatingsTable != null)
                {
                    activeSymbolsCount = stockRatingsTable.Rows.Count;

                    var symbolsAmountQuery = from c in stockRatingsTable.AsEnumerable()
                                             select new SymbolInfo { Symbol = c.Field<string>("Symbol"), Market = c.Field<string>("Market") };
                    ratingSymbols = symbolsAmountQuery.AsParallel().ToList();
                }

                for (int i = 0; i < activeSymbolsCount; i++)
                {
                    symbol = ratingSymbols.AsParallel().ElementAtOrDefault(i).Symbol;
                    market = ratingSymbols.AsParallel().ElementAtOrDefault(i).Market;
                    ratingList = new List<StockRating>();

                    using (SqlCommand historicalRatingsCommand = new SqlCommand("select * from dbo.OldStockRatings where Symbol='" + symbol + "' and Market='" + market + "'", connection))
                    using (SqlDataAdapter historicalRatingsAdapter = new SqlDataAdapter(historicalRatingsCommand))
                    using (DataTable historicalRatingsTable = new DataTable("historicalratings"))
                    {
                        // fill the historical ratings table
                        historicalRatingsAdapter.Fill(historicalRatingsTable);

                        if (historicalRatingsTable != null)
                        {
                            completedRowCount = historicalRatingsTable.AsEnumerable().AsParallel().Count();
                            completedDates = historicalRatingsTable.AsEnumerable().AsParallel().Select(d => d.Field<DateTime>("Date")).ToList();
                        }
                    }
                            using (SqlCommand amexCommand = new SqlCommand("select * from dbo.DailyAmexData where Symbol='" + symbol + "'", connection))
                            using (SqlDataAdapter amexAdapter = new SqlDataAdapter(amexCommand))
                            using (DataTable amexTable = new DataTable("amexdata"))
                            {
                                // fill the amex data table
                                amexAdapter.Fill(amexTable);

                                if (amexTable != null)
                                {
                                    var amexFillQuery = from c in amexTable.AsEnumerable()
                                                        select new StockData { Close = c.Field<decimal>("Close"), Date = c.Field<DateTime>("Date"), High = c.Field<decimal>("High"), Low = c.Field<decimal>("Low"), Volume = c.Field<Int64>("Volume") };
                                    amexList = amexFillQuery.AsParallel().ToList();

                                    rowCount = amexList.AsParallel().Count();
                                }
                            }

                    Parallel.For(0, rowCount - 30, new ParallelOptions
                    {
                        MaxDegreeOfParallelism = Environment.ProcessorCount
                    }, async j =>
                    {
                                if (amexList.AsParallel().Count() > 0)
                                {
                                    date = amexList.AsParallel().ElementAtOrDefault(j).Date;
                                    searchDate = date.Subtract(TimeSpan.FromDays(60));

                                    if (completedDates.Contains(date) == false)
                                    {
                                        var amexQuery = from c in sandpInfo
                                                        where c.Date >= searchDate && c.Date <= date
                                                        join d in amexList on c.Date equals d.Date
                                                        select new StockMarketCompare { stockClose = d.Close, marketClose = c.Close };

                                        var amexStockDataQuery = from c in amexList
                                                                 where c.Date >= searchDate && c.Date <= date
                                                                 select new StockData { Close = c.Close, High = c.High, Low = c.Low, Volume = c.Volume, Date = c.Date };

                                        stockCompareData = amexQuery.AsParallel().ToList();
                                        stockData = amexStockDataQuery.AsParallel().ToList();
                                        isGoodToGo = true;
                                    }
                                    else
                                    {
                                        isGoodToGo = false;
                                    }
                                }

                        if (completedDates.Contains(date) == false)
                        {
                            var sandpDateQuery = from c in sandpInfo
                                                 where c.Date >= searchDate && c.Date <= date
                                                 select c;
                            sandpDateInfo = sandpDateQuery.AsParallel().ToList();
                            symbolClass = new SymbolInfo() { Symbol = symbol, Market = market };
                            isGoodToGo = true;
                        }
                        else
                        {
                            isGoodToGo = false;
                        }

                        if (isGoodToGo)
                        {
                            StockRating rating = performCalculations(symbolClass, date, sandpInfo, stockData, stockCompareData);

                                if (rating != null)
                                {
                                    **ratingList.Add(rating);** // getting the exception thrown here
                                }
                        }
                    });

                    // now save the results to the table outside the parallel for loop

                    ratingList.RemoveAll(item => item == null);
                    List<StockRating> masterList = ratingList.DistinctBy(j => j.date).ToList();
                    saveToTable(masterList, symbol, market);
                    // close the connection
                    connection.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                // close the connection
                connection.Close();
            }
        }
    }

List<T> 不是线程安全的,您正在从 Parallel.For 内部调用 .Add。您需要锁定 Add 或在 System.Collections.Concurrent 命名空间中使用线程安全集合。

这不是您遇到的唯一线程错误,例如,也在您的 Parallel.For 中,您分配了几个变量,这些变量都在循环外的范围内声明。您的各种线程将相互写入并分配这些值。

ratingsList 不是线程安全的,因为不能保证 List<T> 是线程安全的(静态方法除外),但您正在从多个线程修改它。

Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe. It is safe to perform multiple read operations on a List, but issues can occur if the collection is modified while it’s being read. To ensure thread safety, lock the collection during a read or write operation. To enable a collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization. For collections with built-in synchronization, see the classes in the System.Collections.Concurrent namespace. For an inherently thread–safe alternative, see the ImmutableList class.

https://msdn.microsoft.com/en-us/library/6sh2ey19%28v=vs.110%29.aspx

改用 thread safe collection