偶尔显示ArgumentOutOfException错误

时间:2018-10-03 17:06:06

标签: c# multithreading

下面的C#程序是一个股票应用程序,可以模拟股票的涨跌。我知道当索引超出for循环范围时会引发ArgumentOutOfException。但是,在多次重新运行该程序之后,我注意到该异常只是偶尔抛出。运行该程序10次后,在该行上将异常抛出1-4次

stocks.remove(stocks[i]).

看我的程序,它的线程同步部分是否引起了问题?如果不是,那么是什么原因导致偶尔抛出该异常,而不是根本不引发或每次运行都引发该异常?

Stock.cs

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

namespace Lab3
{
    //delegate to display stock information
    public delegate void StockNotification(String stockName, double currentValue, double initialV, int stockquantity);

public class Stock
{
    public System.Object lockThis = new System.Object(); //lock each stock object for raising and invoking event
    private String name; 
    private double initValue; //starting or initial value
    private int stkAmt;
    private double current; //curent stock value
    private int numChanges; //number of times stock value changes
    private int maxChange; //the range within a stock can change every time unit
    private double notifyThreshold; //the threshold at which collection of customers for a stock are notified
    static string[] operations = { "+", "-" }; //array of operations to change stock value (+ or -)

    Thread thr; //thread object

    /*default constructor*/
    public Stock() { }

    /*constructor w/ given parameter values*/
    public Stock(String stkName, int starting, int amt, int max, int thresh)
    {
        name = stkName;
        initValue = starting;
        current = initValue;
        stkAmt = amt;
        maxChange = max;
        notifyThreshold = thresh;
        thr = new Thread(new ThreadStart(Activate));
        thr.Start(); //start thread
    }

    /*property that controls the variable stockAmt*/
    public int StockQuantity
    {
        get { return stkAmt; }
        set { stkAmt = value; }
    }

    /*read-only property that gets name of stock*/
    public string StockName
    {
        get { return name; }
    }

    /*changes stock value after a period of time 
     end method when # of changes to stock value match 
     stock quantity*/
    public void Activate()
    {          
        numChanges = 0;
        for (; ; )
        {
            Thread.Sleep(500);
            ChangeStockValue();
            if (numChanges == stkAmt)
            {
                return;
            }
        }

    }

    /*change stock value - add number between 1 and some specified number
     raise event if difference between current and starting values exceed threshold*/
    public void ChangeStockValue()
    {
        Random rnd = new Random();
        int oper = rnd.Next(operations.Length);
        if(oper == 0) //add random number to current stock value
        {
            current += rnd.Next(10, maxChange); 
        }
        else if(oper == 1) //subtract random number from current stock value
        {
            current -= rnd.Next(10, maxChange);
        }
        numChanges++;
        if (numChanges == stkAmt) //# of times stock values changes matches stock's quantity
        {
            return;
        }
        else
        {
            double gainRounded = Math.Round(((current - initValue) / initValue) * 100,2); //get stock gain/loss

            //stock gain/loss is outside stock's threshold range (ex: -5% to 20% w/ 20 being threshold)
            if (gainRounded > this.notifyThreshold || gainRounded < this.notifyThreshold * -1)
            {
                lock(lockThis) //raise stock event 1 stock obj at a time
                {
                    RaiseEvent(); //send data to StockEventData object; send object attributes to listener
                }                  
            }
        }
    }

    /*this method sends stock information to the event listener
     event type StockNotification is invoked*/
    protected virtual void RaiseEvent()
    {
        StockEventData stock = new StockEventData();
        stock.stkName = name;
        stock.currentVal = current;
        stock.initialVal = initValue;
        stock.quantityStk = stkAmt;            
        StockEvent?.Invoke(stock.stkName, stock.currentVal, stock.initialVal, stock.quantityStk);
    }       
    public event StockNotification StockEvent; //event of type StockNotification               
}

/*this class brings stock data to the event listener*/
public class StockEventData : EventArgs
{
    public String stkName { get; set; }
    public double currentVal { get; set; }
    public double initialVal { get; set; }
    public int quantityStk { get; set; }
}   
}

StockCustomer.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Lab3
{
    public class StockCustomer
    {
        private String customer;
        private List<Stock> stocks;
        private readonly System.Object lock1 = new System.Object(); //lock object for displaying stock info in file
        private double gainThreshold; //customer's personal threshold

    /*default constructor*/
    public StockCustomer(String name, double gainLimit)
    {
        customer = name;
        gainThreshold = gainLimit;
        stocks = new List<Stock>();
    }

    /*add stock to a list
     @param s - stock object*/
    public void AddStock(Stock s)
    {
        stocks.Add(s);
        s.StockEvent += Notify; //add stock to the event handler
    }

    /*event handler that displays stock information in console and file
     @param stkName - stock name
     @param currentVal - current stock value
     @param initVal - initial stock value
     @param quantityStk - stock quantity*/
    private void Notify(string stkName, double currentVal, double initVal, int stockQuantity)
    {
        string net = ""; //string that will contain either gain or loss text
        double gainOrLoss = Math.Round(((currentVal - initVal) / initVal) * 100.00, 2); //get stock value

        //if stock gain is outisde range of customer's threshold, sell the stock
        if (gainOrLoss > gainThreshold || gainOrLoss < gainThreshold*-1)
        {
            if(gainOrLoss < gainThreshold * -1) //customer's stock value is a loss
            {
                gainOrLoss *= -1;
                net = "% loss";
            }
            else //customer's stock value is a gain
            {
                net = "% gain";
            }

            //remove stock from list after displaying in console and file
            for (int i = 0; i < stocks.Count; i++)
            {
                if (stocks[i].StockName == stkName)
                {
                    //display stock info to console
                    Console.WriteLine(this.customer.PadRight(12) + stkName.PadRight(12) + initVal.ToString().PadRight(5) +
               currentVal.ToString().PadRight(7) + gainOrLoss + net);

                    // Create a string array with lines of stock info, current date and time
                    string[] lines = { DateTime.Now.ToString("MM/dd/yyyy").PadRight(12),
            DateTime.Now.ToString("hh:mm:ss").PadRight(13),this.customer.PadRight(12),
            stkName.PadRight(12), initVal.ToString().PadRight(5),
            currentVal.ToString().PadRight(7) + gainOrLoss + net};

                    // Set a variable to the My Documents path.
                    string mydocpath1 = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                    mydocpath1 = Path.Combine(mydocpath1, "WriteLines.txt");                       

                    lock (lock1) //1 stock object displayed in file; repeats for other stock objects
                    {
                        //create a stream for a file
                        using (FileStream fs = new FileStream(mydocpath1, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                        {
                            // Write the string array to a new file named "WriteLines.txt".                
                            using (StreamWriter outputFile = new StreamWriter(fs))
                            {                                   
                                foreach (string line in lines)
                                {
                                    outputFile.Write(line); //write stock info to file
                                }
                                outputFile.WriteLine("");
                                outputFile.Close();
                                outputFile.Dispose();

                            }
                            fs.Close();
                            fs.Dispose();
                        }                           
                    }
                    stocks.Remove(stocks[i]);
                }
            }
        }                        
    }
}
}

Program.cs(主要方法)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Lab3
{    

class Program
{
    static void Main(string[] args)
    {           
        Stock stock1 = new Stock("Technology", 160, 25, 20, 30);
        Stock stock2 = new Stock("Retail", 140, 20, 10, 15);
        Stock stock3 = new Stock("Banking", 90, 25, 15, 25);
        Stock stock4 = new Stock("Commodity", 500, 30, 20, 22);
        StockCustomer b1 = new StockCustomer("Andrew",28);
        b1.AddStock(stock1);
        b1.AddStock(stock4);
        b1.AddStock(stock3);
        b1.AddStock(stock2);
        StockCustomer b2 = new StockCustomer("Harrison",25);
        b2.AddStock(stock3);
        b2.AddStock(stock4);
        b2.AddStock(stock2);
        b2.AddStock(stock1);
        StockCustomer b3 = new StockCustomer("Jimmy",30);
        b3.AddStock(stock2);
        b3.AddStock(stock1);
        b3.AddStock(stock3);
        b3.AddStock(stock4);           
        Console.ReadKey();
    }       
}
}

1 个答案:

答案 0 :(得分:0)

出现异常的原因是,您在循环“向上”数组时要删除循环中的项。假设您的数组中有2个项目,而您删除了第一个。在第二遍循环中,您将尝试访问位置1处的项目,但是数组现在在位置0处仅包含一项,因此例外。

在这种情况下,我采取的方法是始终向后遍历数组:

for (int i = stocks.Count - 1; i >= 0; i--)