WinRT DownloadProgress callback Progress Status - c#

I am writing a universal app primarily targeting Windows Phone using SQLite-net.
During the course of operation, the user is presented with an option to download multiple files. At the end of each file download, I need to mark the file in the db as completed. I am using BackgroundDownloader in order to download files - the WP8.0 app used Background Transfer Service and that worked great. Files can be huge (some 200+ mbs, user content) and i am not looking forward to wrapping the downloads in the HttpClient or WebClient.
However, it seems that the progress callback doesn't work with awaits unless I actually breakpoint in the method.
The following are listings from a sample app i quickly put together that demonstrates the behaviour:
Model:
public class Field
{
[PrimaryKey]
[AutoIncrement]
public int Id { get; set; }
public bool Done { get; set; }
}
MainPage codebehind (i am creating a db here only for the purposes of this example!):
private async void Button_Click(object sender, RoutedEventArgs e)
{
using (var db = new SQLiteConnection(Windows.Storage.ApplicationData.Current.LocalFolder.Path + "//Main.db"))
{
db.CreateTable<Field>();
db.Commit();
}
this.DbConnection = new SQLiteAsyncConnection(Windows.Storage.ApplicationData.Current.LocalFolder.Path + "//My.db");
var dl = new BackgroundDownloader();
dl.CostPolicy = BackgroundTransferCostPolicy.Always;
var transferUri = new Uri("http://192.168.1.4/hello.world", UriKind.Absolute);
var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(
"Content",
CreationCollisionOption.OpenIfExists);
var localFile = await folder.CreateFileAsync("cheesecakes.file", CreationCollisionOption.ReplaceExisting);
var d = dl.CreateDownload(transferUri, localFile);
d.Priority = BackgroundTransferPriority.High;
var progressCallback = new Progress<DownloadOperation>(this.DownloadProgress);
await d.StartAsync().AsTask(progressCallback);
}
private async void DownloadProgress(DownloadOperation download)
{
Debug.WriteLine("Callback");
if (download.Progress.Status == BackgroundTransferStatus.Completed)
{
var f = new Field();
f.Done = true;
await this.DbConnection.InsertAsync(f);
Debug.WriteLine("DONE");
}
}
If i breakpoint inside the DownloadProgress and then press F5 i get both Debug messages, and my db gets a new record.
However, if i just let the code execute, i never see "DONE" printed to me and neither is my db updated.
I tried wrapping the code in a new task:
await Task.Run(
async () =>
{
Debug.WriteLine("taskrun");
.... OTHER CODE FROM ABOVE...
});
But again, i only get to see 'taskrun' if i breakpoint in the callback.
UPDATE I actually think this is more related to checking the status. E.g. the statements outside of the check are executed, but only once, whereas anything inside the check is not executed.
Is there any way to force that callback to be invoked when the download is completed?

private async void DownloadProgress(DownloadOperation download)
{
Debug.WriteLine("Callback");
var value = download.Progress.BytesReceived * 100 download.Progress.TotalBytesToReceive;
new System.Threading.ManualResetEvent(false).WaitOne(1000);
if (download.Progress.Status == BackgroundTransferStatus.Completed )
{
var f = new Field();
f.Done = true;
await this.DbConnection.InsertAsync(f);
Debug.WriteLine("DONE");
}
}
I had this problem too, and I solved this by sleeping for 1000 ms, which worked really well for me.

Not sure what is causing this, but I was able to get the sample app to work reliably by manually checking the bytes to download as opposed to relying on the DownloadOperation.Progress.Status:
private async void DownloadProgress(DownloadOperation download)
{
Debug.WriteLine("Callback");
var value = download.Progress.BytesReceived * 100 / download.Progress.TotalBytesToReceive;
if (download.Progress.Status == BackgroundTransferStatus.Completed || value >= 100)
{
var f = new Field();
f.Done = true;
await this.DbConnection.InsertAsync(f);
Debug.WriteLine("DONE");
}
This gets me to 'DONE' every time.

Related

Task.WhenAll() not working as expected without visual studio debugger attached

I have a question about async programming and Task.WhenAll(). I have a code snippet that downloads a folder from google drive and it works as expected when i am debugging my code. However when I run the application without debugger the download function takes atleast 4x the time it takes when it runs with the debugger. I also get crash logs with TaskCancelled exceptions (System.Threading.Tasks.TaskCanceledException: A task was canceled.) which do not happen with the debugger attached. What needs to be changed for the code to work as expected without debugger attached. NB this snippet downloads +- 1000 files in about 22-25 seconds with debugger and 2min+ without debugger.
public static async Task<bool> DownloadFolder(CloudDataModel.File file, string path, params string[] exclude)
{
try
{
if (file != null && !string.IsNullOrEmpty(file.id))
{
List<string> toExclude = new List<string>();
if(exclude != null)
{
toExclude = exclude.ToList();
}
List<Task> downloadFilesTask = new List<Task>();
var files = await file.GetFiles();
foreach (var f in files)
{
var task = f.Download(path);
downloadFilesTask.Add(task);
}
var folders = await file.GetFoldersAsync();
foreach (var folder in folders)
{
if (toExclude.Contains(folder.name))
{
continue;
}
Task task = null;
if (path.Equals(Statics.ProjectFolderName))
{
task = DownloadFolder(folder, folder.name);
}
else
{
task = DownloadFolder(folder, Path.Combine(path, folder.name));
}
downloadFilesTask.Add(task);
}
var array = downloadFilesTask.ToArray();
await Task.WhenAll(array);
return true;
}
}
catch (Exception e)
{
Crashes.TrackError(e);
}
return false;
}
Edit
after some more trial and error the fault has been identified.
the downloading of the file was the cause of the unexpected behaviour
public static async Task<StorageFile> DownloadFile(CloudDataModel.File file, string destinationFolder)
{
try
{
if (file != null)
{
Debug.WriteLine($"start download {file.name}");
if (file.mimeType == Statics.GoogleDriveFolderMimeType)
{
Debug.WriteLine($"did not download resource, resource was folder instead of file. mimeType: {file.mimeType}");
return null;
}
var endpoint = Path.Combine(DownloadFileEndpoint, $"{file.id}?alt=media");
// this would cause the unexpected behaviour
HttpResponseMessage response = await client.GetAsync(endpoint);
StorageFile downloadedFile;
using (Stream streamToReadFrom = await response.Content.ReadAsStreamAsync())
{
var memstream = new MemoryStream();
StreamReader reader = new StreamReader(streamToReadFrom);
streamToReadFrom.Position = 0;
await streamToReadFrom.CopyToAsync(memstream);
downloadedFile = await fileHandler.SaveDownloadedCloudFile(memstream, file, destinationFolder);
Debug.WriteLine($"download finished {file.name}");
}
return downloadedFile;
}
return null;
}
catch (Exception e)
{
Crashes.TrackError(e);
return null;
}
}
After setting a timeout to the client (System.Net.Http.HttpClient) the code executed as expected.
client.Timeout = new TimeSpan(0,0,5);
So after some trial and error the downloading of the file was identified as the cause of the problem.
var task = f.Download(path);
the Download function implements a System.Net.Http.HttpClient wich was initialized without any kind of timeout. when one of the tasks had a timeout, all other tasks would also not execute as expected. to fix this issue a timeout of 5 seconds was added to the HttpClient object.
private static void InitializeClient()
{
HttpClientHandler handler = new HttpClientHandler { AllowAutoRedirect = true };
client = new HttpClient(handler);
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccountManager.LoggedInAccount.Token);
client.Timeout = new TimeSpan(0,0,5);
}
After this change the code would execute as expected.
My guess is that without the debugger the parallelism increases, and so the remote server is overflowed by requests and can't perform optimally. Your code does not have any provision for limiting the degree of concurrency/parallelism. One possible way to solve this problem is to use a SemaphoreSlim, as shown in this question (among many others).

Xamarin.Android MVVM removes View after opening BarcodeScanner or Camera

I have currently a problem with my C#-Xamarin.Android project.
We have to implement a barcode scanner plus functionality to take photos.
For the barcode scanner we use "Zxing.Net" and for the pictures "Xam.Media.Plugin".
Everything is working alright. Just until I am in their views don't finish the job and minimize the app. After maximizing it again, you can not navigate back - it just minimizes the app again. It seems that the previous views (MvxFragments) are getting destroyed.
I tried to move the picture functionality in a separate MvxViewModel but it is even worse because I have to return a string
public string FileName {get; set; } to the previous view. With await _navigationService.Close(this, FileName) (this = CameraViewModel) it results in a NullReferenceException coming from the previous ViewModel.
(1)
Navigation from (any) ViewModel1 to CameraViewModel:
var fileName = await _navigationService.Navigate<CameraViewModel, MyObject, string>(myObject);
Taking picture
public override async Task Initialize()
{
try
{
FileName = await TakePicture();
await _navigationService.Close(this, FileName);
}
catch (Exception e)
{
// handle exception
}
}
public async Task<string> TakePicture()
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
UserDialogs.Instance.Alert(Lang.NoCamera, Lang.Error, Lang.Ok);
return null;
}
string fileName = $"{}" // being generated here from different params
using var file = await CrossMedia.Current.TakePhotoAsync(cameraOptions);
if (file != null && file.GetStream().Length > 0)
{
using var ms = new MemoryStream();
await file.GetStream().CopyToAsync(ms);
file.Dispose();
// now save picture
return fileName;
}
return null;
}
Now after I navigate from ViewModel1 to CameraViewModel it opens the camera. After minimizing and maximizing again the app I can't navigate back -> it crashes.
If I move the code back to ViewModel1 and redoing all those steps it's just minimizing the application like there's no previous view to navigate back to. Taking a picture and hoping that it functions again doesn't work either. It gets minimized again.
(2)
Starting the barcode scanner from (any) ViewModel
public async Task DoScan()
{
var result = await _barcodeService.Read();
await Task.Delay(new TimeSpan(0, 0, 0, 1));
if (result.Success)
{
UserDialogs.Instance.Toast(result.Barcode);
BarcodeChanged(this, new BarcodeChangedEventArgs { Barcode = result.Barcode });
}
}
Barcode-Read
public async Task<BarcodeResult> Read(BarcodeReadConfiguration config, CancellationToken token)
{
config ??= BarcodeReadConfiguration.Default;
var scanner = new MobileBarcodeScanner
{
UseCustomOverlay = false
};
token.Register(scanner.Cancel);
var scanTask = scanner.Scan(GetXingConfig(config));
await Task.Delay(500, token);
scanner.Torch(true);
var result = await scanTask;
scanner.Torch(false);
return string.IsNullOrWhiteSpace(result?.Text) ? BarcodeResult.Fail : new BarcodeResult(result.Text, result.BarcodeFormat);
}
Same scenario as 1.. while the scanner is open and I minimize + maximize the app and then back press it just minimizes the app again. Even after reading a barcode.
Anyone knows why and how to fix it? Would help a lot..!
Edit: Here is a demo you can work with: https://github.com/softforgery/StackOverflow_Problem

Xamarin form :How to search in listview

I have listview containing data from web API. I want to search in the listview with character wise. The problem I am facing is when I start searching, it works fine but it gets very very slow. I need some solution to fix it. Here is my code:
private async void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(" http://172.16.4.212:51583/api/GetItems");
var admtPatients = JsonConvert.DeserializeObject<List<tblItem>>(json);
ObservableCollection<tblItem> trends = new ObservableCollection<tblItem>(admtPatients);
if (string.IsNullOrEmpty(medicine.Text))
{
MyListView.ItemsSource = trends;
}
else
{
MyListView.ItemsSource = trends
.Where(x =>
x.strItemName.ToLowerInvariant().Contains(e.NewTextValue.ToLowerInvariant()) ||
x.strItemName.ToUpperInvariant().Contains(e.NewTextValue.ToUpperInvariant()));
}
//await ((MainViewModel)this.BindingContext).LoadCountNotificationAsync();
}
Each time Entry_TextChanged is triggered, the call to GetStringAsync is done, which is very time consuming. This means that whenever the user presses a key a call to the API is made. This is why it is so slow.
You are better off calling GetStringAsync in the page's OnAppearing (for example), and saving the result globally:
private List<tblItem> listOfTableItems = new List<tblItem>();
protected override void OnAppearing()
{
var json = await httpClient.GetStringAsync("http://172.16.4.212:51583/api/GetItems");
listOfTableItems = JsonConvert.DeserializeObject<List<tblItem>>(json);
}
Then, in your Entry_TextChanged you reference listOfTableItems from the examples above:
if (String.IsNullOrEmpty(e.NewTextValue))
{
MyListView.ItemsSource = new ObservableCollection<tblItem>(listOfTableItems);
}
else
{
MyListView.ItemsSource = new ObservableCollection<tblItem>(listOfTableItems
.Where(x => x.strItemName.ToLowerInvariant().Contains(e.NewTextValue.ToLowerInvariant())));
}

uwp c# async method waiting data not completely loaded exception

I will try to tell my problem in as simple words as possible.
In my UWP app, I am loading the data async wise on my Mainpage.xaml.cs`
public MainPage()
{
this.InitializeComponent();
LoadVideoLibrary();
}
private async void LoadVideoLibrary()
{
FoldersData = new List<FolderData>();
var folders = (await Windows.Storage.StorageLibrary.GetLibraryAsync
(Windows.Storage.KnownLibraryId.Videos)).Folders;
foreach (var folder in folders)
{
var files = (await folder.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate)).ToList();
FoldersData.Add(new FolderData { files = files, foldername = folder.DisplayName, folderid = folder.FolderRelativeId });
}
}
so this is the code where I am loading up a List of FolderData objects.
There in my other page Library.xaml.cs I am using that data to load up my gridview with binding data.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
try
{
LoadLibraryMenuGrid();
}
catch { }
}
private async void LoadLibraryMenuGrid()
{
MenuGridItems = new ObservableCollection<MenuItemModel>();
var data = MainPage.FoldersData;
foreach (var folder in data)
{
var image = new BitmapImage();
if (folder.files.Count == 0)
{
image.UriSource = new Uri("ms-appx:///Assets/StoreLogo.png");
}
else
{
for (int i = 0; i < folder.files.Count; i++)
{
var thumb = (await folder.files[i].GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.VideosView));
if (thumb != null) { await image.SetSourceAsync(thumb); break; }
}
}
MenuGridItems.Add(new MenuItemModel
{
numberofvideos = folder.files.Count.ToString(),
folder = folder.foldername,
folderid = folder.folderid,
image = image
});
}
GridHeader = "Library";
}
the problem I am facing is that when i launch my application, wait for a few seconds and then i navigate to my library page, all data loads up properly.
but when i try to navigate to library page instantly after launching the app, it gives an exception that
"collection was modified so it cannot be iterated"
I used the breakpoint and i came to know that if i give it a few seconds the List Folder Data is already loaded properly asyncornously, but when i dnt give it a few seconds, that async method is on half way of loading the data so it causes exception, how can i handle this async situation? thanks
What you need is a way to wait for data to arrive. How you fit that in with the rest of the application (e.g. MVVM or not) is a different story, and not important right now. Don't overcomplicate things. For example, you only need an ObservableCollection if you expect the data to change while the user it looking at it.
Anyway, you need to wait. So how do you wait for that data to arrive?
Use a static class that can be reached from everywhere. In there put a method to get your data. Make sure it returns a task that you cache for future calls. For example:
internal class Data { /* whatever */ }
internal static class DataLoader
{
private static Task<Data> loaderTask;
public static Task<Data> LoadDataAsync(bool refresh = false)
{
if (refresh || loaderTask == null)
{
loaderTask = LoadDataCoreAsync();
}
return loaderTask;
}
private static async Task<Data> LoadDataCoreAsync()
{
// your actual logic goes here
}
}
With this, you can start the download as soon as you start the application.
await DataLoader.LoadDataAsync();
When you need the data in that other screen, just call that method again. It will not download the data again (unless you set refresh is true), but will simply wait for the work that you started earlier to finish, if it is not finished yet.
I get that you don't have enough experience.There are multiple issues and no solution the way you are loading the data.
What you need is a Service that can give you ObservableCollection of FolderData. I think MVVM might be out of bounds at this instance unless you are willing to spend a few hours on it. Though MVVM will make things lot easier in this instance.
The main issue at hand is this
You are using foreach to iterate the folders and the FolderData list. Foreach cannot continue if the underlying collection changes.
Firstly you need to start using a for loop as opposed to foreach. 2ndly add a state which denotes whether loading has finished or not. Finally use observable data source. In my early days I used to create static properties in App.xaml.cs and I used to use them to share / observe other data.

WinRT C#: Cannot save UnhandledException to Storage

I'm working on WinRT. If an unhandled exception is thrown I want to write the message text to the storage.
I added an Event handler in 'App.xaml.cs', see the code.
The exception is caught but the last line, where the file is written, crashes again -> 'exception'!
Why? Any idea?
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
this.UnhandledException += App_UnhandledException;
}
async void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder;
StorageFile file= await folder.CreateFileAsync("crash.log",CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, e.Message); // <----- crash again -----
}
Thanks
Sunny
I've been wondering the same thing and stumbled across this quite early on in my search. I've figured out a way, hopefully this will prove useful to someone else too.
The problem is that await is returning control of the UI thread and the app's crashing. You need a deferral but there's no real way to get one.
My solution is to use the settings storage, instead. I'm assuming most people wanting to do this want to do something LittleWatson style, so here's some code modified from http://blogs.msdn.com/b/andypennell/archive/2010/11/01/error-reporting-on-windows-phone-7.aspx for your convenience:
namespace YourApp
{
using Windows.Storage;
using Windows.UI.Popups;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
public class LittleWatson
{
private const string settingname = "LittleWatsonDetails";
private const string email = "mailto:?to=you#example.com&subject=YourApp auto-generated problem report&body=";
private const string extra = "extra", message = "message", stacktrace = "stacktrace";
internal static void ReportException(Exception ex, string extraData)
{
ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;
exceptionValues[extra] = extraData;
exceptionValues[message] = ex.Message;
exceptionValues[stacktrace] = ex.StackTrace;
}
internal async static Task CheckForPreviousException()
{
var container = ApplicationData.Current.LocalSettings.Containers;
try
{
var exceptionValues = container[settingname].Values;
string extraData = exceptionValues[extra] as string;
string messageData = exceptionValues[message] as string;
string stacktraceData = exceptionValues[stacktrace] as string;
var sb = new StringBuilder();
sb.AppendLine(extraData);
sb.AppendLine(messageData);
sb.AppendLine(stacktraceData);
string contents = sb.ToString();
SafeDeleteLog();
if (stacktraceData != null && stacktraceData.Length > 0)
{
var dialog = new MessageDialog("A problem occured the last time you ran this application. Would you like to report it so that we can fix the error?", "Error Report")
{
CancelCommandIndex = 1,
DefaultCommandIndex = 0
};
dialog.Commands.Add(new UICommand("Send", async delegate
{
var mailToSend = email.ToString();
mailToSend += contents;
var mailto = new Uri(mailToSend);
await Windows.System.Launcher.LaunchUriAsync(mailto);
}));
dialog.Commands.Add(new UICommand("Cancel"));
await dialog.ShowAsync();
}
}
catch (KeyNotFoundException)
{
// KeyNotFoundException will fire if we've not ever had crash data. No worries!
}
}
private static void SafeDeleteLog()
{
ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;
exceptionValues[extra] = string.Empty;
exceptionValues[message] = string.Empty;
exceptionValues[stacktrace] = string.Empty;
}
}
}
To implement it, you need to do the same as the link above says, but to ensure the data's here in case the url ever goes down:
App.xaml.cs Constructor (BEFORE the call to this.InitializeComponent()):
this.UnhandledException += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");
Obviously if you already have an UnhandledException method you can throw the call to LittleWatson in there.
If you're on Windows 8.1, you can add a NavigationFailed call too. This needs to be in an actual page (typically MainPage.xaml.cs or whatever page is first opened):
xx.xaml.cs Constructor (any given page):
rootFrame.NavigationFailed += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");
Lastly, you need to ask the user if they want to send the e-mail when the app re-opens. In your app's default Page's constructor (default: the page App.xaml.cs initializes):
this.Loaded += async (s, e) => await LittleWatson.CheckForPreviousException();
Or add the call to your OnLoad method if you already use it.
In this situation, await could be loosely translated to "do this job on another thread, and continue what you were doing while you wait for it to finish". Given that what your app was doing was crashing, you probably don't want it to continue doing that until you're done logging the problem. I'd suggest running your file IO synchronously in this case.
This may come a bit too late for the original question but...
as #Hans Passant suggested, avoiding await (i.e., running the FileIO.AppendTextAsync() synchronously), also seconded by #Jon, I would opt for this rather than the relatively too heavy code for LittleWatson. As the app is in some error handing state anyway (this should be a rare occurrence) I wouldn't put any blocking arising from synchronous (due to removing await) as a major downside.
Leaving the synchronous option to one side, the following await implementation worked for me:
Change await FileIO.AppendTextAsync(file, e.Message); to:
Task task = LogErrorMessage(file, e.Message)
task.Wait(2000); // adjust the ms value as appropriate
...
private async Task LogErrorMessage(StorageFile file, string errorMessage)
{
await FileIO.AppendTextAsync(file, errorMessage); // this shouldn't crash in App_UnhandledException as it did before
}

Categories

Resources