none
System.NullReferenceException occurs occasionally when using LINQ to sort data stored in a List<>. RRS feed

  • Question

  • Hi,

    I have a form program like this:

    namespace mynamespace { class myclass { // Create a class to store some value public class FinalResultItem { public uint R { set; get; } public uint N { set; get; } public double Result { set; get; } public double Error { set; get; } } // Use a List<> to store all the results from different threads public volatile List<FinalResultItem> FinalResult; // Define a delegate of Calculate function private delegate void CalculateDelegate(int a); // main int main() { int[] a = GetValue(); // Get some input value FinalResult = new List<FinalResultItem> { }; // Initialize the FinalResult List FinalResult.Clear(); // Clear the FinalResult List // For a better performance on multi-core PC, I assign one thread for each value of "a" to do the calculation. for (int i = 0; i < a.Length; i++) { CalculateDelegate CD = new CalculateDelegate(Calculate); CD.BeginInvoke(a[i], null, null); } // Wait Until All Delegate Threads Are Ended

                do

                 {

                } while (bIsAllDone == false); // Check If Error Occured Among All Delegate Threads // Ensure there is something in the list if (FinalResult.Count > 0) { // Use LINQ to sort the List IEnumerable<FinalResultItem> result = from item in FinalResult orderby item.Error, item.R, item.N select item; Output(result); // output the result } } // Calculate function void Calculate(int a) { for (int i = 0; i < 10; i++) // Use a for loop to check all possibility { FinalResultItem FRI = new FinalResultItem(); FRI = GetResult(a, i); // Get Result if (FRI.Error < Threshold) // If Result Error is acceptable { FinalResult.Add(FRI); // Add the result to FinalResult List } } } } }

    And it works well except the following part throws a System.NullReferenceException occasionally.

    // Use LINQ to sort the List
    IEnumerable<FinalResultItem> result = from item in FinalResult 
    orderby item.Error, item.R, item.N 
    select item;

    The exception is the following,

    /*
    System.NullReferenceException
      HResult=0x80004003
      Message=Object reference not set to an instance of an object.
      Source=PLL Divider Calculator
      StackTrace:
       at PLL_Divider_Calculator.Form1.<>c.<StartSingleRun>b__32_0(FinalResultItem item) in D:\SoftwareDevelopment\VS2016\PLL Divider Calculator\PLL Divider Calculator\Form1.cs:line 510
       at System.Linq.EnumerableSorter`2.ComputeKeys(TElement[] elements, Int32 count)
       at System.Linq.EnumerableSorter`1.Sort(TElement[] elements, Int32 count)
       at System.Linq.OrderedEnumerable`1.<GetEnumerator>d__1.MoveNext()
       at PLL_Divider_Calculator.Form1.StartSingleRun() in D:\SoftwareDevelopment\VS2016\PLL Divider Calculator\PLL Divider Calculator\Form1.cs:line 521
       at PLL_Divider_Calculator.Form1.StartCalculation() in D:\SoftwareDevelopment\VS2016\PLL Divider Calculator\PLL Divider Calculator\Form1.cs:line 368
       at PLL_Divider_Calculator.Form1.pictureBox7_Click(Object sender, EventArgs e) in D:\SoftwareDevelopment\VS2016\PLL Divider Calculator\PLL Divider Calculator\Form1.cs:line 680
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at PLL_Divider_Calculator.Program.Main() in D:\SoftwareDevelopment\VS2016\PLL Divider Calculator\PLL Divider Calculator\Program.cs:line 19
    */

    By checking the value under debug mode, I find out the cause of the exception is the "item" is null. 

    But I can't tell, according to the logic in the program, why there will be a null value in the list. 

    And the weird thing is, for the same group of input value "a", sometimes it can give the correct result and sometimes it throws the exception.

    Approximate one exception in every 10 runs.

    And I just find out,

    1. If I set the platform to "x86" (which was "x64"), the program seems not to throw the exception any more. 

    2. The more items in the FinalResult List, the higher possibility of exception.

    But I need this program to run under x64 platform as it contains lots of multiplication of double type data, which is much faster than running under x86.

    Could anyone please help me solve it? Thanks a lot!



    • Edited by xuanli317 Sunday, November 25, 2018 7:47 PM
    Sunday, November 25, 2018 7:38 PM

Answers

  • I have figured it out!

    .

    As it turns out, there is null data in the FinalResult List when the exception occurs.

    The problem comes from the following code which is in the function Calculate(),

    FinalResult.Add(FRI); // Add the result to FinalResult List

    .

    When different threads execute the line above at the same time, there is a chance for a "null" instead of "FRI" to be added to the list.

    An easy way to solve it is to use a lock statement.

    // lock object, declare this within the main class
    private readonly object AddItemLock = new object();
    
    ...
    
    lock (AddItemLock)
    {
        FinalResult.Add(FRI); // Add the result to FinalResult List
    }
    .

    Perhaps I have misunderstood the efficacy of the keyword "volatile" in C#:

    https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile

    .

    But finally, it has been solved now and thanks for reading!

    .


    • Edited by xuanli317 Monday, November 26, 2018 8:48 AM
    • Marked as answer by xuanli317 Monday, November 26, 2018 8:49 AM
    Monday, November 26, 2018 8:45 AM

All replies

  • Where is bIsAllDone declared and set initially?

    Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.
    VB Forums - moderator
    profile for Karen Payne on Stack Exchange, a network of free, community-driven Q&A sites

    Sunday, November 25, 2018 7:52 PM
    Moderator
  • Sorry, I forgot to mention that this is not a complete code because I only kept the parts related to the problem and simplified it a little bit.

    The reason I showed the "do-while" loop was to explain that the code will actually check the status of threads instead of using a delay or other method. So the program won't move forward while one or more calculating thread is still running.

    =================================
    The following code will show how I do it, including the declaring and setting of bIsAllDone.

    // I use this array to store and exchange the status of threads
    // -1:Error, 0:Done, 1:Starting, 2:Running
    public volatile int[] ThreadStatus; 
    
    // Delegate of the Calculate function
    private delegate void CalculateDelegate(int ID, int a);
    
    // main
    int main()
    {
        // Get Value and Initialize sth.
        ...
    
        // Initialize ThreadStatus array
        ThreadStatus = new int[a.Length]; 
    
        // Start threads, using the value of i as each thread's ID
        for (int i = 0; i < a.Length; i++)
        {
            ThreadStatus[i] = 1; // Indicate the thread is "Starting"
            CalculateDelegate CD = new CalculateDelegate(Calculate);
            CD.BeginInvoke(i, a[i], null, null);
        }
    
        // Wait Until All Delegate Threads Are Ended
        Boolean bIsAllDone;
        do
        {
            bIsAllDone = true;
    
            for (int i = 0; i < a.Length; i++)
            {
                if ((ThreadStatus[i] == 1) || (ThreadStatus[i] == 2))
                {
                    bIsAllDone = false;
                    break;
                }
            }
    
            Application.DoEvents(); // Process messages to prevent a frozen form
    
        } while (bIsAllDone == false);
    
        // Check If Error Occured Among All Delegate Threads
        for (int i = 0; i < a.Length; i++)
        {
            if (ThreadStatus[i] == -1) // if the thread was ended by an error
            {
                // Release all resources and notify the user
                ...
    
                return;
            }
        }
    
        // Check FinalResult.Count and sort the result using LINQ
        ...
    }
    
    // Calculate function
    void Calculate(int ID, int a)
    {
        try
        {
            ThreadStatus[ID] = 2; // Indicate the thread is "Running"
    
            // Do the Math
            ...
    
            // At the end
            ThreadStatus[ID] = 0; // Indicate the thread is "Done"
        }
        catch (System.Exception)
        {
            ThreadStatus[ID] = -1; // Indicate the thread has error occured
            return;
        }
    }
    

    Thanks for your reply!

    Monday, November 26, 2018 4:50 AM
  • I have figured it out!

    .

    As it turns out, there is null data in the FinalResult List when the exception occurs.

    The problem comes from the following code which is in the function Calculate(),

    FinalResult.Add(FRI); // Add the result to FinalResult List

    .

    When different threads execute the line above at the same time, there is a chance for a "null" instead of "FRI" to be added to the list.

    An easy way to solve it is to use a lock statement.

    // lock object, declare this within the main class
    private readonly object AddItemLock = new object();
    
    ...
    
    lock (AddItemLock)
    {
        FinalResult.Add(FRI); // Add the result to FinalResult List
    }
    .

    Perhaps I have misunderstood the efficacy of the keyword "volatile" in C#:

    https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile

    .

    But finally, it has been solved now and thanks for reading!

    .


    • Edited by xuanli317 Monday, November 26, 2018 8:48 AM
    • Marked as answer by xuanli317 Monday, November 26, 2018 8:49 AM
    Monday, November 26, 2018 8:45 AM