Search code examples
c#windows-runtimewinrt-async

Unexpected behavior of static variable initialization


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)


Solution

  • 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 */
            }
        }
    }