Windows 8 - How to get nested folder properly? - c#

I have a GridView that's bound to a collection of objects which load images from the disk.
The objects are put onto a stack when they become visible, and the images are loaded off the stack sequentially.
The problem is that GetFolderAsync() doesn't return until the ScrollViewer containing the objects has stopped scrolling.
The code is as below:
public static async Task<StorageFolder> GetFileFolderAsync(String fileUrl)
{
try
{
string filePathRelative = DownloadedFilePaths.GetRelativeFilePathFromUrl(fileUrl);
string[] words = filePathRelative.Split('\\');
StorageFolder currentFolder = await DownloadedFilePaths.GetAppDownloadsFolder();
for (int i = 0; (i < words.Length - 1); i++)
{
//this is where it "waits" for the scroll viewer to slow down/stop
currentFolder = await currentFolder.GetFolderAsync(words[i]);
}
return currentFolder;
}
catch (Exception)
{
return null;
}
}
I've pinpointed it down to that line where it gets the folder that contains the image. Is this even the proper way to get a nested folder?

You could try to use ConfigureAwait(false) to run the for loop on a thread pool thread:
public static async Task<StorageFolder> GetFileFolderAsync(String fileUrl)
{
try
{
string filePathRelative = DownloadedFilePaths.GetRelativeFilePathFromUrl(fileUrl);
string[] words = filePathRelative.Split('\\');
// HERE added ConfigureAwait call
StorageFolder currentFolder = await
DownloadedFilePaths.GetAppDownloadsFolder().ConfigureAwait(false);
// Code that follows ConfigureAwait(false) call will (usually) be
// scheduled on a background (non-UI) thread.
for (int i = 0; (i < words.Length - 1); i++)
{
// should no longer be on the UI thread,
// so scrollviewer will no longer block
currentFolder = await currentFolder.GetFolderAsync(words[i]);
}
return currentFolder;
}
catch (Exception)
{
return null;
}
}
Note that in the above case since there is no work that is done on the UI, you CAN use ConfigureAwait(false). For example, the following would not work because there is a UI related call after the ConfigureAwait:
// HERE added ConfigureAwait call
StorageFolder currentFolder = await
DownloadedFilePaths.GetAppDownloadsFolder().ConfigureAwait(false);
// Can fail because execution is possibly not on UI thread anymore:
myTextBox.Text = currentFolder.Path;

It turns out the method I was using to determine the object's visibility was blocking the UI thread.
I have a GridView that's bound to a collection of objects which load images from the disk.
The objects are put onto a stack when they become visible, and the images are loaded off the stack sequentially.
The problem is that GetFolderAsync() doesn't return until the ScrollViewer containing the objects has stopped scrolling.

Related

DataGridViewImageCell async image load

I need to populate a column in a DataGridView with a thumbnail image. I would like to load the DataGridViewImageCell.Value asynchronously as it does take some time to download the images.
This solution loads the images asynchronously, but it appears to prevent the UI thread from performing other tasks (I assume because the application's message queue is filled with the .BeginInvoke calls).
How can this be accomplished yet still allow the user to scroll through the grid while images are being downloaded?
private void LoadButton_Click(object sender, EventArgs e)
{
myDataGrid.Rows.Clear();
// populate with sample data...
for (int index = 0; index < 200; ++index)
{
var itemId = r.Next(1, 1000);
var row = new DataGridViewRow();
// itemId column
row.Cells.Add(new DataGridViewTextBoxCell
{
ValueType = typeof(int),
Value = itemId
});
// pix column
row.Cells.Add(new DataGridViewImageCell
{
ValueType = typeof(Image),
ValueIsIcon = false
});
// pre-size height for 90x120 Thumbnails
row.Height = 121;
myDataGrid.Rows.Add(row);
// Must be a "better" way to do this...
GetThumbnailForRow(index, itemId).ContinueWith((i) => SetImage(i.Result));
}
}
private async Task<ImageResult> GetThumbnailForRow(int rowIndex, int itemId)
{
// in the 'real world' I would expect 20% cache hits.
// the rest of the images are unique and will need to be downloaded
// emulate cache retrieval and/or file download
await Task.Delay(500 + r.Next(0, 1500));
// return an ImageResult with rowIndex and image
return new ImageResult
{
RowIndex = rowIndex,
Image = Image.FromFile("SampleImage.jpg")
};
}
private void SetImage(ImageResult imageResult)
{
// this is always true when called by the ContinueWith's action
if (myDataGrid.InvokeRequired)
{
myDataGrid.BeginInvoke(new Action<ImageResult>(SetImage), imageResult);
return;
}
myDataGrid.Rows[imageResult.RowIndex].Cells[1].Value = imageResult.Image;
}
private class ImageResult
{
public int RowIndex { get; set; }
public Image Image { get; set; }
}
Methods like ContinueWith() are since the introduction of async-await fairly out-of-date. Consider using real async-await
Every now and then your thread has to wait for something, wait for a file to be written, wait for a database to return information, wait for information from a web site. This is a waste of computing time.
Instead of waiting, the thread could look around to see it if could do something else, and return later to continue with the statements after the wait.
Your function GetThumbNail for row simulates such a wait in the Task.Delay. Instead of waiting, the thread goes up it's call stack to see it its caller is not awaiting for the result.
You forgot to declare your LoadButton_Click async. Therefore your UI isn't responsive.
To keep a UI responsive while an event handler is busy, you have to declare your event handler async and use awaitable (async) functions whenever possible.
Keep in mind:
a function with a await should be declared async
every async function returns Task instead of void and Task<TResult> instead of TResult
The only exception to this is the event handler. Although it is declared async, it returns void.
if you await a Task the return is void; if you await a Task<TResult> the return is TResult
So your code:
private async void LoadButton_Click(object sender, EventArgs e)
{
...
// populate with sample data...
for (int index = 0; index < 200; ++index)
{
...
ImageResult result = await GetThumbnailForRow(...);
}
}
private async Task<ImageResult> GetThumbnailForRow(int rowIndex, int itemId)
{
...
await Task.Delay(TimeSpan.FromSeconds(2));
return ...;
}
Now whenever the await in your GetThumbnailForRow is met, the thread goes up its call stack to see if the caller is not awaiting the result. In your example the caller is awaiting, so it goes up its stack to see... etc. Result: whenever your thread isn't doing anything your user interface is free to do other things.
However you could improve your code.
Consider to start loading the thumbnail as at the beginning or your event handler. You don't need the result immediately and there are other useful things to do. So don't await for the result, but do those other things. Once you need the result start awaiting.
private async void LoadButton_Click(object sender, EventArgs e)
{
for (int index = 0; index < 200; ++index)
{
// start getting the thumnail
// as you don't need it yet, don't await
var taskGetThumbNail = GetThumbnailForRow(...);
// since you're not awaiting this statement will be done as soon as
// the thumbnail task starts awaiting
// you have something to do, you can continue initializing the data
var row = new DataGridViewRow();
row.Cells.Add(new DataGridViewTextBoxCell
{
ValueType = typeof(int),
Value = itemId
});
// etc.
// after a while you need the thumbnail, await for the task
ImageResult thumbnail = await taskGetThumbNail;
ProcessThumbNail(thumbNail);
}
}
If getting thumbnails is independently waiting for different sources, like waiting for a web site and a file, consider starting both functions and await for them both to finish:
private async Task<ImageResult> GetThumbnailForRow(...)
{
var taskImageFromWeb = DownloadFromWebAsync(...);
// you don't need the result right now
var taskImageFromFile = GetFromFileAsync(...);
DoSomethingElse();
// now you need the images, start for both tasks to end:
await Task.WhenAll(new Task[] {taskImageFromWeb, taskImageFromFile});
var imageFromWeb = taskImageFromWeb.Result;
var imageFromFile = taskImageFromFile.Result;
ImageResult imageResult = ConvertToThumbNail(imageFromWeb, imageFromFile);
return imageResult;
}
Or you could start getting all thumbnails without await and await for all to finish:
List<Task<ImageResult>> imageResultTasks = new List<Task<ImageResult>>();
for (int imageIndex = 0; imageIndex < ..)
{
imageResultTasks.Add(GetThumbnailForRow(...);
}
// await for all to finish:
await Task.WhenAll(imageResultTasks);
IEnumerable<ImageResult> imageResults = imageResulttasks
.Select(imageResultTask => imageResultTask.Result);
foreach (var imageResult in imageResults)
{
ProcesImageResult(imageResult);
}
If you have to do some heavy calculations, without waiting for something, consider creating an awaitable async function to do this heavy calculation and let a separate thread do these calculations.
Example: the function to convert the two images could have the following async counterpart:
private Task<ImageResult> ConvertToThumbNailAsync(Image fromWeb, Image fromFile)
{
return await Task.Run( () => ConvertToThumbNail(fromWeb, fromFile);
}
An article that helped me a lot, was Async and Await by Stephen Cleary
The analogy to prepare a meal described in this interview with Eric Lippert helped me to understand what happens when your thread encounters an await. Search somewhere in the middle for async-await
Start by making your event handler async:
private async void LoadButton_Click(object sender, EventArgs e)
Then change this line:
GetThumbnailForRow(index, itemId).ContinueWith((i) => SetImage(i.Result));
to:
var image = await GetThumbnailForRow(index, itemId);
SetImage(image);

Cross-thread operation not valid: Control 'lbDatabase' accessed from a thread other than the thread it was created on

I'm working on a fingerprint and I need to make this operation and I get an error, and don't know how to fix it
private void doVerify(object sender, DoWorkEventArgs args)
{
VerificationResult verificationResult = new VerificationResult();
for (int i = 0; i < lbDatabase.Items.Count || verificationResult.score > 0; i++)
{
lbDatabase.Invoke(new MethodInvoker(delegate { lbDatabase.SelectedItem = i; }));
verificationResult.score = _engine.Verify(((CData)lbDatabase.SelectedItem).EngineUser, 20000, out verificationResult.engineStatus);
}
args.Result = verificationResult;
}
Error: Cross-thread operation not valid: Control 'lbDatabase' accessed
from a thread other than the thread it was created on
You have three places in your code where you access lbDatabase.
The loop - lbDatabase.Items.Count
Setting the selected item - lbDatabase.SelectedItem = i
Retrieving the selected item - lbDatabase.SelectedItem
I assume on the second one you meant to write lbDatabase.SelectedIndex = i.
Only the second one you are invoking. So the other two are running on the background worker thread. That's why you're getting the error.
All access to UI elements - reading and writing - needs to be done on the UI thread.
Now, since you're trying to do this using a background worker you have a method that will freeze up the UI if it solely ran on the UI thread. So you need the parts that access the control to run on the UI, but everything else to run on the background thread.
The next problem is though, that calling lbDatabase.Invoke(new MethodInvoker(delegate { lbDatabase.SelectedItem = i; })); pushes the instruction to the UI, but there is no guarantee that the code will immediately run - in fact the entire loop might run and queue up all of the invokes. You obviously want it to happen synchronously, but that wont happen.
Looking at your code you clearly only want to access all of the CData items on the lbDatabase list box. There's a very easy way to make this work.
Just try this when calling the background worker:
backgroundWorker1.RunWorkerAsync(lbDatabase.Items.Cast<CData>().ToArray());
And then change your doVerify to this:
private void doVerify(object sender, DoWorkEventArgs args)
{
CData[] items = (CData[])args.Argument;
VerificationResult verificationResult = new VerificationResult();
for (int i = 0; i < items.Length || verificationResult.score > 0; i++)
{
verificationResult.score = _engine.Verify(items[i].EngineUser, 20000, out verificationResult.engineStatus);
}
args.Result = verificationResult;
}
The error is pretty self explantory. You're running on a different thread, so you can't interact with the control. This means accessing lbDatabase.Items.Count is out.
If instead you:
lbDatabase.Invoke((MethodInvoker)(() => {
VerificationResult verificationResult = new VerificationResult();
for (int i = 0; i < lbDatabase.Items.Count || verificationResult.score > 0; i++)
{
lbDatabase.SelectedItem = i;
verificationResult.score = _engine.Verify(((CData)lbDatabase.SelectedItem).EngineUser, 20000, out verificationResult.engineStatus);
}
args.Result = verificationResult;
}));
then you'd probably be back in business. Now all of the accesses to the control are queued to run on the UI thread (and you're no longer switching contexts with Invoke in the middle of a loop... costly).

Win8 Store app: How to make popup ui call in sync way in bg render thread

I can't understand how can I implement sync MessageBox/QueryBox/QueryStringBox calls in my Monogame Windows Store application.
In realizations of our engine on other platforms (Android, WP7/8, IOS) we call MessageBox/QueryBox/QueryStringBox wrapper in sync way:
if (SysDlg.QRESULT_YES == SysDlg.showQueryBox(APP_NAME, "Save data?", S_YES, S_NO))
{
//Save data
}
else
{
//do something else
}
It's the most convenient way, as gui code becomes linear. Such calls are made inside a background render thread, so it's ok if we wait inside SysDlg.showQueryBox. Pseudocode looks like this:
int standardOpenDlg(int dlgType, <some arguments>)
{
msDlgActive = true;
< async construct and show dlg on main UI thread with handler like below >
dlg.onButtonPress += () =>
{
dlg.close();
msDlgResult = 1;
msDlgActive = false;
}
while (msDlgActive)
EngineSystem.Sleep(100);
//when user presses button msDlgActive becomes false and we quit endless loop and return msDlgResult
return msDlgResult;
}
In my current case I have such MessageBox code:
var messageDialog = new MessageDialog(msDlgText, msDlgTitle);
for (int i = 0; i < cnt; i++)
messageDialog.Commands.Add(new UICommand(buttonsTexts[i],
new UICommandInvokedHandler(MsgBoxInputFinishedCallBack), (int)i));
messageDialog.DefaultCommandIndex = 0;
messageDialog.ShowAsync();
And buttons' callback:
private static void MsgBoxInputFinishedCallBack(IUICommand command)
{
// get the result
if (command != null && command.Id != null)
{
int result = (int)command.Id;
msDlgResult = (int)result;
}
msDlgActive = false;
}
I've tried to wrap messagebox creation and call in
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {...} );
But if I block my background draw thread after that I never see the message box. It looks like MainGame.Update (it executes exactly in this bg thread) should finish and return control to the system so that my code on main thread can be executed.
If I call (even in my thread) and then continue execution (but also leave caller context without msDlgResult ) I messagebox appears as expected.
I also tried
messageDialog.ShowAsync();
await messageDialog.ShowAsync();
Task t = messageDialog.ShowAsync().AsTask();
t.Wait();
but nothing helps.
In Windows Phone 8 I just called Guide.BeginShowMessageBox with callback and everything worked fine.
I suppose I don't quite understand this new Metro and async/await (although I know what is it).
Please tell how can I wait in bg thread until messagebox closed?
Maybe I should use some other winRT UI for that?
Thanks!
------Update
If anybody cares - finally I had to create/show dialogbox and handle results in two different places.

How to make thread safe calls from FileSystemWatcher

I am writing an application with two parts, one part downloads data and lists its sources in a file which is being monitored by the other part which, every 15 minutes when the data is downloaded therefore updating the file, it loads the file contents and removes the old data. I currently have this code:
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
try
{
fsw.EnableRaisingEvents = false;
MessageBox.Show("File Changed: " + e.FullPath);
_times.Clear();
XmlDocument dataset = new XmlDocument();
dataset.Load(#"C:\Users\Henry\AppData\Local\{9EC23EFD-F1A4-4f85-B9E9-729CDE4EF4C7}\cache\DATA_RAINOBS\dataset.xml");
for (int x = 0; x < dataset.SelectNodes("//Times/Time").Count; x++)
{
_times.Add(
new Time()
{
Original = dataset.SelectNodes("//Times/Time/#original")[x].InnerText,
Display = dataset.SelectNodes("//Times/Time/#display")[x].InnerText,
Directory = dataset.SelectNodes("//Times/Time/#directory")[x].InnerText + "_LORES.png"
});
}
times.SelectedIndex = 0;
}
finally { fsw.EnableRaisingEvents = true; }
}
But when I run it, I get a System.NotSupportedException and from further information I know that it is because I am trying to manipulate a list from a separate thread created by the FileSystemWatcher.
I have literally done hardly any programming using multiple threads so I have no idea what to do. It would be very helpful if someone could modify the code above so that it is thread safe and will work properly because then I will have something to learn from and it won't be wrong. How do I make this work?
You have to use Invoke on the control (or its owner). The method will then be queued for processing on the control's thread, rather than the FSW's thread.
On WPF, this is handled by a dispatcher for this, eg.
times.Dispatcher.Invoke(() => { yourCode; });
If you're expecting your code to take some time, you might consider only doing the invoke with a full list of items at the end, rather than invoking the whole operation.
Your _times collection is binded with GUI part so error must be at line
_times.Clear();
WPF has constraint that you cannot modify source collection which is binded to GUI from thread other than UI thread. Refer to my answer over here for details.
As stated above you can modify it from only UI thread so consider dispatching this stuff on UI dispatcher which will queue this on UI thread. Get UI dispatcher like this:
App.Current.Dispatcher.Invoke((Action)delegate
{
_times.Clear();
});
Also make sure any subsequent calls related to UI is dispatched on UI thread. May be wrap it under one call only:
XmlDocument dataset = new XmlDocument();
dataset.Load(#"C:\Users\Henry\AppData\Local\{9EC23EFD-F1A4-4f85-B9E9-
729CDE4EF4C7}\cache\DATA_RAINOBS\dataset.xml");
App.Current.Dispatcher.Invoke((Action)delegate
{
_times.Clear();
for (int x = 0; x < dataset.SelectNodes("//Times/Time").Count; x++)
{
_times.Add(
new Time()
{
Original = dataset.SelectNodes("//Times/Time/#original")
[x].InnerText,
Display = dataset.SelectNodes("//Times/Time/#display")
[x].InnerText,
Directory = dataset.SelectNodes("//Times/Time/#directory")
[x].InnerText + "_LORES.png"
});
}
_times.SelectedIndex = 0;
});

Thread makes application halt

I'm currently trying to create a FileViewer control, and after I've added Items (Filenames with Icons, size, etc) to my ListView (Icon - Filename - Extension - Size) I also check if the file is an image (png/jpg, etc) but this I do on a different Thread.
My expectation of a thread is that it runs beside the main application, but after I've added all my files I start this thread. It checks all files in the ListView and creates thumbnails for them. If done correctly, ListView icons should appear one after one as they're loaded - but they're not. They all appear at the same time.
...and I can't do anything while the Thread is active.
Why is this happening and what am I doing wrong? I've dealt with Threads before and it's always worked, I invoke the method with a Callback.
Flow of the Thread:
Format file key = "C:\image.png" = "C_image_png".
Check if thumbnail to image exists (by checking it's key), then use it
Else load thumbnail with Image.FromFile().GetThumbnailImage() and add image with Key to Listview's images
Finally change the ImageKey of the ListView item.
All done in a thread.
private void GetFiles()
{
// Load all files in directory
Thread t = new Thread(new ThreadStart(GetImageFiles));
t.Priority = ThreadPriority.Lowest;
t.Start();
}
delegate void GetImageFilesCallback();
private void GetImageFiles()
{
if (this.IsHandleCreated)
{
if (files.InvokeRequired)
{
GetImageFilesCallback callback = new GetImageFilesCallback(GetImageFiles);
this.Invoke(callback);
}
else
{
string extension = "";
string key = "";
foreach (string file in _files)
{
extension = FileManager.GetExtension(file);
key = (DirectoryCurrent + file).Replace(":", "").Replace("\\", "_").Replace(".", "_");
foreach (string knownimages in _knownImageTypes)
{
if (extension.ToLower() == knownimages)
{
foreach (ListViewItem item in files.Items)
{
if (item.Text == file)
{
if (files.SmallImageList != null)
{
if (files.SmallImageList.Images[key] == null)
{
files.SmallImageList.Images.Add(key, Image.FromFile(DirectoryCurrent + file).GetThumbnailImage(16, 16, null, IntPtr.Zero));
files.LargeImageList.Images.Add(key, Image.FromFile(DirectoryCurrent + file).GetThumbnailImage(32, 32, null, IntPtr.Zero));
}
files.Items[item.Index].ImageKey = key;
}
}
}
}
}
}
files.Refresh();
}
}
}
The method that your thread calls is invoking itself onto the main thread, and then doing all the work in that thread, thereby blocking your UI.
You should arrange your code so that the thread code does not touch the ListView, but just loads each image, then invokes a main-thread method, passing the bitmaps so that the main thread can assign them to the ListView.
Here's a sketch of what I mean:
// this is your thread method
// it touches no UI elements, just loads files and passes them to the main thread
private void LoadFiles(List<string> filenames) {
foreach (var file in filenames) {
var key = filename.Replace(...);
var largeBmp = Image.FromFile(...);
var smallBmp = Image.FromFile(...);
this.Invoke(new AddImagesDelegate(AddImages), key, largeBmp, smallBmp);
}
}
// this executes on the main (UI) thread
private void AddImages(string key, Bitmap large, Bitmap small) {
// add bitmaps to listview
files.SmallImageList.Images.Add(key, small);
files.LargeImageList.Images.Add(key, large);
}
private delegate AddImagesDelegate(string key, Bitmap large, Bitmap small);
Read the following: http://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke. The important thing with Invoke and BeginInvoke is that they both operate on the Main thread. BeginInvoke just doesn't wait for the message to be processed before returning control. Eventually though, the work will happen on the Main thread and will block until it is complete.

Categories

Resources