将线程安全变量与并行循环 C# 结合使用

Using a thread safe variable with a parallel for loop C#

这是我第一次使用并行 for 循环,我了解基础知识,正如您在下面的代码中看到的那样,但我不了解如何使循环线程内的变量安全。

我正在关注 https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-write-a-parallel-for-loop-with-thread-local-variables

上的文章

我目前不断收到以下错误:在对数据执行计算时,序列在我的计算 class 中不包含任何元素。我是否遗漏了一些让所有线程安全的简单方法?

更新:我为计算添加了所有相关代码 class,以一个方法为例,该方法返回常量序列不包含任何元素异常以及我到目前为止为尝试解决该问题所做的工作(例外情况仍在继续)

更新 2:我在我的代码中添加了自定义 classes,现在应该可以编译了。

public static async Task Test()
    {
        Vector<double> vectorArrayBuy = null;
        Vector<double> vectorArraySell = null;
        Calculations calcTemp = null;

        try
        {
            using (financeEntities context = new financeEntities())
            {
                List<string> symbolList = new List<string>();
                symbolList = GetStockSymbols("nasdaq");

                foreach (string symbol in symbolList)
                {
                    var query = await context.DailyStockDatas.Where(i => i.Symbol == symbol && i.Market == "nasdaq").ToListAsync();
                    if (query.Count >= 200)
                    {
                        List<MultipleRegressionInfo> listMRInfo = new List<MultipleRegressionInfo>();
                        Calculations calc = new Calculations(query, j);
                        calcTemp = calc;

                        Parallel.For(0, 200, j =>
                        {
                            var targetValueBuy = calc.ListCalculationData.Select(i => i.MRTargetValueBuy).ToList();
                            var targetValueSell = calc.ListCalculationData.Select(i => i.MRTargetValueSell).ToList();
                            vectorArrayBuy = CreateVector.Dense(targetValueBuy.ToArray());
                            vectorArraySell = CreateVector.Dense(targetValueSell.ToArray());
                            var name = calc.ListCalculationData.First();
                            IEnumerable<double> value;

                            value = calc.ListCalculationData.Select(i => i.WilliamsR);
                            MultipleRegressionInfo r1 = Rn(value, vectorArrayBuy, nameof(name.WilliamsR), j, calc);
                            listMRInfo.Add(r1);
                        });

class Calculations
{
    public List<DailyStockData> Data { get; set; }
    public ConcurrentBag<CalculationData> ListCalculationData { get; set; }

    public Calculations(List<DailyStockData> dailyData, int days)
    {
        lock (thisLock)
        {
            Data = dailyData;

            // initiate the data
            ListCalculationData = new ConcurrentBag<CalculationData>();

            for (int i = 0; i < Data.Count; i++)
            {
                var currentDate = Data.ElementAt(i).Date;

                CalculationData calc = new CalculationData(currentCalcData);
                calc.WilliamsR = CalculateWilliamsR(days, currentDate);

                // add current calculator class to the list
                ListCalculationData.Add(calc);
            }
        }
    }
public double CalculateWilliamsR(int days, DateTime startingDate)
    {
        double williamsR = 0;
        double highestHigh = 0;
        double currentClose = 0;
        double lowestLow = 0;

        try
        {
            highestHigh = FindMaxOrMin(days, startingDate, MaxOrMinType.HighestHigh);
            lowestLow = FindMaxOrMin(days, startingDate, MaxOrMinType.LowestLow);
            currentClose = (double)Data.Where(i => i.Date <= startingDate).Last().Close;
            williamsR = -100 * ((highestHigh - currentClose) / (highestHigh - lowestLow));
        }
        catch (Exception ex)
        {
            williamsR = 0;
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
        }

        return williamsR;
    }

    public enum MaxOrMinType
    {
        HighestHigh,
        LowestLow,
        HighestClose,
        LowestClose
    }

    public double FindMaxOrMin(int days, DateTime startingDate, MaxOrMinType type)
    {
        double maxMin = 0;

        try
        {
            lock (thisLock)
            {
                switch (type)
                {
                    // gets Sequence contains no elements exceptions at all of the below lines
                    case MaxOrMinType.HighestClose:
                        maxMin = (double)Data.Where(i => i.Date <= startingDate).Take(days).Max(i => i.Close);
                        break;
                    case MaxOrMinType.HighestHigh:
                        maxMin = (double)Data.Where(i => i.Date <= startingDate).Take(days).Max(i => i.High);
                        break;
                    case MaxOrMinType.LowestClose:
                        maxMin = (double)Data.Where(i => i.Date <= startingDate).Take(days).Min(i => i.Close);
                        break;
                    case MaxOrMinType.LowestLow:
                        maxMin = (double)Data.Where(i => i.Date <= startingDate).Take(days).Min(i => i.Low);
                        break;
                    default:
                        break;
                }
            }
        }
        catch (Exception ex)
        {
            maxMin = 0;
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
        }

        return maxMin;
    }

public class DailyStockData
{
    public DailyStockData();

    public int ID { get; set; }
    public string Symbol { get; set; }
    public string Market { get; set; }
    public DateTime Date { get; set; }
    public decimal Open { get; set; }
    public decimal High { get; set; }
    public decimal Low { get; set; }
    public decimal Close { get; set; }
    public decimal AdjustedClose { get; set; }
    public long Volume { get; set; }
}

public class CalculationData
{
    public CalculationData(CalculationData calcData)
    {
        Date = calcData.Date;
        Open = calcData.Open;
        High = calcData.High;
        Low = calcData.Low;
        Close = calcData.Close;
        AdjustedClose = calcData.AdjustedClose;
        Volume = calcData.Volume;
        WilliamsR = calcData.WilliamsR;
}

    public CalculationData() { }

    public DateTime Date { get; set; }
    public double Open { get; set; }
    public double High { get; set; }
    public double Low { get; set; }
    public double Close { get; set; }
    public double AdjustedClose { get; set; }
    public double Volume { get; set; }
    public double WilliamsR { get; set; }
}

Sequence contains no elements

对于这个问题,问题在于获取 empty setMax(即其中没有数据的集合)。因此:

maxMin = (double)Data.Where(i => i.Date <= startingDate).Take(days).Max(i => i.Close);

失败。要解决此问题,请将其更改为:

maxMin  = Data.Where(i => i.Date <= startingDate).Take(days)
    .OrderByDescending(z => z.Close)
    .Select(z => (double?)z.Close)
    .FirstOrDefault() ?? 0;

OrderByDescending 将确保最高的 Close 列在最前面。 Select 将确保返回 Close 值(如果根本没有条目,则返回 null)。如果没有匹配项,?? 0 会将 null 转换为 0(将 0 更改为对您的目的有意义的任何值。

对于不同的方法,考虑 https://github.com/morelinq/MoreLINQ/issues/28 .

您需要以类似的方式更改每个 switch 语句以解决问题(CloseCloseHighLow,并且 OrderByDescendingOrderByDescendingOrderBy)。

此外,在您的原始代码中,您在没有更早的 OrderBy 的情况下执行 Take 很奇怪,但我暂时忽略它。

Thread safety

就调用listMRInfo.Add的线程安全性而言——而不是做:

List<MultipleRegressionInfo> listMRInfo = new List<MultipleRegressionInfo>();

Parallel.For(0, 200, j =>
{
    // Code here
    listMRInfo.Add(r1);
});

考虑做:

var listMRInfo = Enumerable.Range(0, 200)
    .AsParallel()
    .Select(j =>
    {
        // Code here
        return r1;
    })
    .ToList();

这将允许您拥有与 For 相同的基本 Parallel 行为,但也允许您拥有 List 作为结果(以线程安全方式)。