Asynchronous FileIO methods mixing up images in WinRT application

0

So I have a method implemented in my application that downloads images for a given manga from a site, and places said images into the app's AppData (localstate) folder, in a language folder inside a title folder.

Image downloading works perfectly, aside from the fact that, if I try download multiple image sets at once, the results are mixed among the folders involved.

The method that deals with File IO:

private async Task<bool> SaveManga(Data.Posts.Content2 manga)
    {
        var folder = ApplicationData.Current.LocalFolder;
        var safeName = Path.GetInvalidFileNameChars().Aggregate(manga.ContentInfo.ContentName, (current, c) => current.Replace(c, '_').Replace('.', '_'));

        // Create folder for issue if does not exist
        var issueFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(safeName, CreationCollisionOption.OpenIfExists);
        // Create folder for language if does not exist
        var langFolder = await issueFolder.CreateFolderAsync(manga.ContentInfo.ContentLanguage, CreationCollisionOption.OpenIfExists);

        // Populate pages if page count is 0
        if (manga.Pages == null)
            manga.Pages = new Dictionary<int, Data.Posts.Page>();
        if (manga.Pages.Count == 0)
        {
            if (NetworkInterface.GetIsNetworkAvailable() == false)
            {

            }
            var pages = Fakku.GetMangaInfo(manga.ContentInfo.ContentUrl).Result.Pages;
            for (var i = 0; i < pages.Count; i++)
            {
                var page = new Data.Posts.Page { Image = pages.Values.ElementAt(i).Image, Thumb = pages.Values.ElementAt(i).Thumb };
                manga.Pages.Add(i, page);
            }
        }
        var files = await langFolder.GetItemsAsync();
        var fileCount = files.Count;
        if (fileCount + 3 == manga.ContentInfo.ContentPages) return true;
        var cbi = new BitmapImage(new Uri(manga.ContentInfo.ContentImages.Cover));
        const string cname = "cover.jpg";
        var wcbi = await WriteableBitmapFromBitmapImageExtension.FromBitmapImage(cbi);
        await wcbi.SaveToFile(langFolder, cname, CreationCollisionOption.OpenIfExists);


        var sbi = new BitmapImage(new Uri(manga.ContentInfo.ContentImages.Sample));
        const string sname = "sample.jpg";
        var wsbi = await WriteableBitmapFromBitmapImageExtension.FromBitmapImage(sbi);
        await wsbi.SaveToFile(langFolder, sname, CreationCollisionOption.OpenIfExists);

        for (var i = 1; i < manga.ContentInfo.ContentPages; i++)
        {
            var bi = new BitmapImage(new Uri(manga.Pages[i].Image));
            //var x = await langFolder.CreateFileAsync(String.Format("{0}.png", i), CreationCollisionOption.ReplaceExisting);
            var imgName = String.Format("{0}.jpg", i);
            try
            {
                var wbi = await WriteableBitmapFromBitmapImageExtension.FromBitmapImage(bi);
                await wbi.SaveToFile(langFolder, imgName, CreationCollisionOption.OpenIfExists);
            }
            catch (Exception e)
            {
                var hResult = (uint)e.HResult;
                if (hResult.Equals(0x80190194))
                {
                    new MessageDialog("Error loading pages. \n  If this is a sample manga, this behavior is to be expected.").ShowAsync();
                    break;
                }
            }
        }
        return true;
    }

And the implementation:

private async void SaveButton_Click(object sender, RoutedEventArgs e)
    {
        SaveButton.IsEnabled = false;
        if (isSaved)
        {
            var i = defaultViewModel.ContentInfo;
            Fakku.SavedManga.RemoveAll(x => x.ContentInfo.ContentUrl == i.ContentUrl);
            isSaved = !isSaved;
            SaveButton.Content = "Save";
            var manga = defaultViewModel;
            await RemoveManga(manga);
            await ProcessSaved();
            SaveButton.IsEnabled = true;
        }
        else if (!isSaved)
        {
            Fakku.SavedManga.Add(defaultViewModel);
            isSaved = true;
            SaveButton.Content = "Remove";
            try
            {
                var manga = defaultViewModel;
                //pendingWork = SaveManga(manga);

                _saves.Enqueue(manga);
                await AsyncParallelForEach(
_saves, async save => await SaveManga(manga), 8,
TaskScheduler.FromCurrentSynchronizationContext());
                //HandleSaving();
                await ProcessSaved();
            }
            catch (NullReferenceException)
            {

            }
            SaveButton.IsEnabled = true;
        }
    }

AsParallel function:

private static Task AsyncParallelForEach<T>(
IEnumerable<T> source, Func<T, Task> body,
int maxDegreeOfParallelism = DataflowBlockOptions.Unbounded,
TaskScheduler scheduler = null)
    {
        var options = new ExecutionDataflowBlockOptions
        {
            MaxDegreeOfParallelism = maxDegreeOfParallelism
        };
        if (scheduler != null)
            options.TaskScheduler = scheduler;

        var block = new ActionBlock<T>(body, options);

        foreach (var item in source)
            block.Post(item);

        block.Complete();
        return block.Completion;
    }

The usage of the parallel foreach is my most recent attempt, taking from this article, but the results mimic what I described above, and I feel I am still missing something very important.

Could anyone point me in the right direction? I'm trying to find a way in which I can either process these downloads, i.e. multiple instances of the SaveManga method simultaneously, or at least queue them for completion.

Edit: I'm starting to realize what I need to focus on is parallelization, not asynchronicity, if I want to run the downloads simultaneously, which is what I'd prefer.

c#
asynchronous
windows-runtime
winrt-async
asked on Stack Overflow Sep 14, 2014 by evanjs • edited May 23, 2017 by Community

1 Answer

0

I think the problem is that you are parallelizing the initialization (folder creation + page count) too and then it mix up everyting.

I would recommend, doing the initialization first :

public async Task InitAsync(Data.Posts.Content2 manga){
    var folder = ApplicationData.Current.LocalFolder;
        var safeName = Path.GetInvalidFileNameChars().Aggregate(manga.ContentInfo.ContentName, (current, c) => current.Replace(c, '_').Replace('.', '_'));

        // Create folder for issue if does not exist
        var issueFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(safeName, CreationCollisionOption.OpenIfExists);
        // Create folder for language if does not exist
        var langFolder = await issueFolder.CreateFolderAsync(manga.ContentInfo.ContentLanguage, CreationCollisionOption.OpenIfExists);

        // Populate pages if page count is 0
        if (manga.Pages == null)
            manga.Pages = new Dictionary<int, Data.Posts.Page>();
        if (manga.Pages.Count == 0)
        {

            var pages = Fakku.GetMangaInfo(manga.ContentInfo.ContentUrl).Result.Pages;
            for (var i = 0; i < pages.Count; i++)
            {
                var page = new Data.Posts.Page { Image = pages.Values.ElementAt(i).Image, Thumb = pages.Values.ElementAt(i).Thumb };
                manga.Pages.Add(i, page);
            }
        }

        var cbi = new BitmapImage(new Uri(manga.ContentInfo.ContentImages.Cover));
        const string cname = "cover.jpg";
        var wcbi = await WriteableBitmapFromBitmapImageExtension.FromBitmapImage(cbi);
        await wcbi.SaveToFile(langFolder, cname, CreationCollisionOption.OpenIfExists);


        var sbi = new BitmapImage(new Uri(manga.ContentInfo.ContentImages.Sample));
        const string sname = "sample.jpg";
        var wsbi = await WriteableBitmapFromBitmapImageExtension.FromBitmapImage(sbi);
        await wsbi.SaveToFile(langFolder, sname, CreationCollisionOption.OpenIfExists);
}

and then only doing some parallelization like this, because the framework will be smart enough to manage the threads :) :

  List<Task> tasksToWait = new List<Task>();
for (var i = 1; i < manga.ContentInfo.ContentPages; i++)
        {
taskToWait.Add(Task.Run(()=>SaveMangaPage(i));
}
await Task.WhenAll(tasksToWait );
await ProcessSaved();

With SaveMangaPage being :

public async Task SaveMangaPage(int pageNumber) {

 var bi = new BitmapImage(new Uri(manga.Pages[pageNumber].Image));
 //var x = await langFolder.CreateFileAsync(String.Format("{0}.png", pageNumber), CreationCollisionOption.ReplaceExisting);
 var imgName = String.Format("{0}.jpg", pageNumber);
 try
   {
  var wbi = await WriteableBitmapFromBitmapImageExtension.FromBitmapImage(bi);
  await wbi.SaveToFile(langFolder, imgName, CreationCollisionOption.OpenIfExists);
 }
 catch (Exception e)
  {
     var hResult = (uint)e.HResult;
     if (hResult.Equals(0x80190194))
     {
                    new MessageDialog("Error loading pages. \n  If this is a sample manga, this behavior is to be expected.").ShowAsync();
                    break;
      }
 }
}
}
answered on Stack Overflow Dec 10, 2015 by Jonathan ANTOINE

User contributions licensed under CC BY-SA 3.0