0

I'm not much familiar with WinRT. I'm encountering an unexpected behavior. I've a static variable _Verses that is initialized in static constructor of class. So expected behavior is _Verses will be initialized before first reference to static method as explained in When is a static constructor called in C#?

But when I call a static async function LoadData (WinRT) I got exception.

Object Reference not set to an instance of object.

My Code is:

public VerseCollection
{
   public const int TotalVerses = 6236;

   static Verse[] _Verses;
   static VerseCollection()
   {
        _Verses = new Verse[TotalVerses];
   }

   internal static async void LoadData(StorageFile file)
   {
      using (var reader = new BinaryReader(await file.OpenStreamForReadAsync()))
      {
           int wId = 0;
           for (int i = 0; i < VerseCollection.TotalVerses; i++)
           {
               var retValue = new string[reader.ReadInt32()];
               for (int j = 0; j < retValue.Length; j++)
                    retValue[j] = reader.ReadString();

               _Verses[i] = new Verse(i, wId, retValue);

               wId += _Verses[i].Words.Count;
           }
       }
   }
}

public Book
{    
   public static async Task<Book> CreateInstance()
   {
       VerseCollection.LoadData(await DigitalQuranDirectories.Data.GetFileAsync("quran-uthmani.bin"));
   }
}

I call the function CreateInstance as:

async void DoInit()
{
    await DigitalQuran.Book.CreateInstance();
}

Same code is working in desktop but not working for WinRT. Full Code of Book Class for Desktop is here and for VerseCollection class is here

EDIT: Complete code is here

public class Book : VerseSpan
{
    public static async Task<Book> CreateInstance()
    {
        _Instance = new Book();

        VerseCollection.LoadData(await DigitalQuranDirectories.Data.GetFileAsync("quran-uthmani.bin"));
        PrivateStorage.LoadQuranObjectsFromMetadata();
        // Some Other Operations too

        return _Instance;
    }
}


public class VerseCollection
{
    static Verse[] _Verses = new Verse[TotalVerses];

    internal static async void LoadData(StorageFile file)
    {
        using (var reader = new BinaryReader(await file.OpenStreamForReadAsync()))
        {
            int wId = 0;
            for (int i = 0; i < VerseCollection.TotalVerses; i++)
            {
                var retValue = new string[reader.ReadInt32()];
                for (int j = 0; j < retValue.Length; j++)
                    retValue[j] = reader.ReadString();

                _Verses[i] = new Verse(i, wId, retValue);

                wId += _Verses[i].Words.Count;
            }
        }
    }
}

public class Verse 
{
    public Verse(int number, int firstWordIndex, string[] words)
    {
        GlobalNumber = number + 1;

        Words = new WordCollection(firstWordIndex, words, this);            
    }
}

public class WordCollection : ReadOnlyCollection<Word>
{
    public const int TotalWords = 77878;

    static Word[] _Words = new Word[TotalWords];
    static string[] _WordsText = new string[TotalWords];

    public WordCollection(int startIndex, int count)
        : base(count)
    {
        this.startIndex = startIndex;
    }

    internal WordCollection(int startId, string[] words, Verse verse) : this(startId, words.Length)
    {
        int max = words.Length + startId;
        for (int i = startId; i < max; i++)
        {
            _Words[i] = new Word(i, verse);
            _WordsText[i] = words[i - startId];            
        }
    }
}

public abstract class ReadOnlyCollection<T> : IEnumerable<T>
{
    public ReadOnlyCollection(int count)
    {
        Count = count;
    }
}

public class PrivateStorage
{
    internal static async void LoadQuranObjectsFromMetadata()
    {            
        using (var reader = new BinaryReader(await (await DigitalQuranDirectories.Data.GetFileAsync(".metadata")).OpenStreamForReadAsync()))
        {
            /* 1 */ ChapterCollection.LoadData(EnumerateChapters(reader));
            /* 2 */ PartCollection.LoadData(EnumerateParts(reader));
            /* Some other tasks */
        }
    }

    static IEnumerator<ChapterMeta> EnumerateChapters(BinaryReader reader)
    {
        for (int i = 0; i < ChapterCollection.TotalChapters; i++)
        {
            yield return new ChapterMeta()
            {
                StartVerse = reader.ReadInt32(),
                VerseCount = reader.ReadInt32(),
                BowingCount = reader.ReadInt32(),
                Name = reader.ReadString(),
                EnglishName = reader.ReadString(),
                TransliteratedName = reader.ReadString(),
                RevelationPlace = (RevelationPlace)reader.ReadByte(),
                RevelationOrder = reader.ReadInt32()
            };
        }
    }

    static IEnumerator<PartMeta> EnumerateParts(BinaryReader reader)
    {
        for (int i = 0; i < PartCollection.TotalParts; i++)
        {
            yield return new PartMeta()
            {
                StartVerse = reader.ReadInt32(),
                VerseCount = reader.ReadInt32(),
                ArabicName = reader.ReadString(),
                TransliteratedName = reader.ReadString()
            };
        }
    }
}


public class ChapterCollection : ReadOnlyCollection<Chapter>
{
    public const int TotalChapters = 114;

    static Chapter[] _Chapters = new Chapter[TotalChapters];

    internal static void LoadData(IEnumerator<ChapterMeta> e)
    {
        for (int i = 0; i < TotalChapters; i++)
        {
            e.MoveNext();
            _Chapters[i] = new Chapter(i, e.Current);
        }
    }
}


public class PartCollection : ReadOnlyCollection<Part>
{
    public const int TotalParts = 30;

    static Part[] _Parts = new Part[TotalParts];
    internal static void LoadData(IEnumerator<PartMeta> e)
    {            
        for (int i = 0; i < TotalParts; i++)
        {
            e.MoveNext();
            _Parts[i] = new Part(i, e.Current);
        }
    }
}

When I run the code with debugger no exception is raised. Further After exception visual studio shows some times in class VerseCollection in function LoadData on line _Verses[i] = new Verse(i, wId, retValue); (_Verses is null) and some times in class ChapterCollection in Function LoadData on line _Chapters[i] = new Chapter(i, e.Current); (_Chapters is null)

Community
  • 1
  • 1
Adnan Umer
  • 3,669
  • 2
  • 18
  • 38
  • 1
    We do not see how you are initializing _Verses, maybe you are using an async task? if that's the case, the async task may not have finished when you call LoadData... – Gusman Jun 10 '14 at 17:52
  • We have to see your initialization code of the static variable – Yuval Itzchakov Jun 10 '14 at 18:07
  • _Verses is not initialized using async task. It is just init of an array. Initialization code added. – Adnan Umer Jun 10 '14 at 18:32
  • Why are not awaiting `VerseCollection.LoadData`? – Paulo Morgado Jun 11 '14 at 15:50
  • @PauloMorgado awaiting also doesn't solves the issues. – Adnan Umer Jun 13 '14 at 20:31
  • Does `Verse` refer to `Book` at all? I'm just wondering whether there's some cycle going on here... (If you could possibly provide a short but complete program demonstrating the problem, that would really help.) – Jon Skeet Jun 13 '14 at 21:52
  • Unfortunately that's still not complete - and it's certainly not short. Basically you should take a copy of your project and remove as much as you can without it fixing the problem. (Hopefully there'll be *very* little left.) Then post that code, so that we can compile and run it ourselves. (Ideally it shouldn't need to actually use any storage, either.) – Jon Skeet Jun 14 '14 at 06:37

2 Answers2

1

There was issue with asynchronous call. File reading is asynchronous operation in WinRT. As We can't call async method with void return type with await statement. So next instructions executes without waiting for completion of last executing as another Task. This leads to NullReferanceExecption.

I managed to solve my problems by changing return type of all async operations from void to Task and called them with await like in the code below.

public class Book : VerseSpan
{
    public static async Task<Book> CreateInstance()
    {
        _Instance = new Book();

        await VerseCollection.LoadData(await DigitalQuranDirectories.Data.GetFileAsync("quran-uthmani.bin"));
        await PrivateStorage.LoadQuranObjectsFromMetadata();
        // Some Other Operations too

        return _Instance;
    }
}


public class VerseCollection
{
    static Verse[] _Verses = new Verse[TotalVerses];

    internal static async Task LoadData(StorageFile file)
    {
        using (var reader = new BinaryReader(await file.OpenStreamForReadAsync()))
        {
            int wId = 0;
            for (int i = 0; i < VerseCollection.TotalVerses; i++)
            {
                var retValue = new string[reader.ReadInt32()];
                for (int j = 0; j < retValue.Length; j++)
                    retValue[j] = reader.ReadString();

                _Verses[i] = new Verse(i, wId, retValue);

                wId += _Verses[i].Words.Count;
            }
        }
    }
}

public class PrivateStorage
{
    internal static async Task LoadQuranObjectsFromMetadata()
    {            
        using (var reader = new BinaryReader(await (await DigitalQuranDirectories.Data.GetFileAsync(".metadata")).OpenStreamForReadAsync()))
        {
            /* Some tasks */
        }
    }
}
Adnan Umer
  • 3,669
  • 2
  • 18
  • 38
0

Because it is running on Desktop but not WinRT, it leads me to believe there is an issue with your asynchronous call. Because you are doing this asynchronously, there is no gaurantee that the constructor (static or not) will be finished running before the call to LoadData. Make sure that your constructor has finished executing before calling LoadData function, and this should give you consistent behaviour.

TheBlindSpring
  • 631
  • 5
  • 22
  • How can I make static constructor to finish earlier. According to MSDN topic we can't control static constructor call nor call that manually. – Adnan Umer Jun 10 '14 at 18:43
  • 2
    "Because you are doing this asynchronously, there is no gaurantee that the constructor (static or not) will be finished running before the call to LoadData." That certainly wouldn't be the case in the normal CLR. I wonder whether WinRT has different guarantees around static constructors... – Jon Skeet Jun 13 '14 at 20:40