Reading file async still blocks the UI - wpf

I have a simple treeview, when click on treeview item i just load a text file
Here is the code:
private async void NotesTreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var clickedModel = e.NewValue as TreeFileItem;
if (clickedModel != null && File.Exists(clickedModel.FilePath))
{
_viewModel.NoteLoadingInProgress = true;
using (var reader = File.OpenText(clickedModel.FilePath))
{
var fileContent = await reader.ReadToEndAsync();
_viewModel.ActiveNote = fileContent;
}
_viewModel.NoteLoadingInProgress = false;
}
}
This works, but when I click on treeview item the ui is frozen, until file read is complete. Why such behavior? I have other async methods in my code and they don't block ui.
EDIT: it seems the issue not in reading file, but in setting large amount of text to textbox.text properti via databinding, though setting it directly also take a lot of time and make ui freeze

the ui is frozen, until file read is complete
Streams opened with File.OpenText are never asynchronous. As I describe on my blog, you must open the file using a method with an isAsync parameter (set to true) or a fileOptions parameter (including the FileOptions.Asynchronous value).
it seems the issue not in reading file, but in setting large amount of text to textbox.text properti via databinding
Yes, UI elements are not intended to be used with huge amounts of data. You need to use virtualization if you have large amounts of data to display.

Related

WPF: Waiting for an ItemContainerGenerator to have its containers ready

I'm writing a WPF control that contains an ItemsControl. The control adds and removes items based on certain user actions. Once an item has been added, the control needs to access a FrameworkElement inside the ItemTemplate instance that was just created.
I'm using ItemContainerGenerator.ContainerFromIndex to do this. I also get a ContentPresenter back, but it is empty: it appears it takes a few milliseconds on a separate thread to instantiate the template objects.
I read that I need to use ItemContainerGenerator.Status to determine whether or not the containers are fully created, so I wrote the following method:
private async Task<TextBox> GetMainInputControl(int index)
{
// _selectedItemsEditor is the ItemsControl inside my main control that contains the items
var evt = new ManualResetEvent(false);
_selectedItemsEditor.ItemContainerGenerator.StatusChanged += (sender, args) =>
{
var status = _selectedItemsEditor.ItemContainerGenerator.Status;
if (status == GeneratorStatus.ContainersGenerated || status == GeneratorStatus.Error)
{
evt.Set();
}
};
ContentPresenter container = null;
await Task.Run(() =>
{
var status = _selectedItemsEditor.ItemContainerGenerator.Status;
if (status == GeneratorStatus.GeneratingContainers
|| status == GeneratorStatus.NotStarted)
{
evt.WaitOne();
}
container =
_selectedItemsEditor.ItemContainerGenerator.ContainerFromIndex(index) as ContentPresenter;
});
return container?.ContentTemplate.FindName("PART_ItemEditorMainInput", container) as TextBox;
}
I know that there are a few things I need to fix here, but mostly, it just doesn't work, because _selectedItemsEditor.ItemContainerGenerator.Status immediately returns GeneratorStatus.ContainersGenerated, so the code doesn't wait - but then the code container?.ContentTemplate.FindName throws an exception indicating that the container is NOT ready.
How can I make this work, or alternatively use a better way of achieving this?
That code looks like you're trying to access ui controls on a background thread. So I'm not at all surprised it doesn't work.
There are two approaches I would consider.
You could defer your code so it waits until the dispatcher ( the ui thread essentially ) has done it's stuff for whatever you just asked it to do.
Application.Current.Dispatcher.InvokeAsync(new Action(() =>
{
// Your code which is to run after the items are rendered
}), DispatcherPriority.ContextIdle);
Or
You could force the layout process so you make the items do their thing. This will potentially lock the ui up whilst it's working. If the user clicks something and his obvious intent is to wait for layout to update or there's not so much going on then this won't be a problem.
You could just call .UpdateLayout() on your control.
https://msdn.microsoft.com/en-us/library/system.windows.uielement.updatelayout(v=vs.110).aspx

How do I determine when Dispatcher.BeginInvoke completes?

I have a Silverlight 5 application that uses ImageTools for Silverlight to save a Canvas to a PNG image. I understand that I need to work with the Canvas on the UI thread and have the following code, which works:
if (saveFileDialog.ShowDialog() == true)
{
var stream = saveFileDialog.OpenFile();
writeableBitmap.Dispatcher.BeginInvoke(delegate
{
ExtendedImage extendedImage = writeableBitmap.ToImage();
new PngEncoder().Encode(extendedImage, stream);
});
}
The problem is that if the Canvas is very large it can take a noticeable time for the code in the BeginInvoke to complete. Since this is running on the UI thread it freezes the browser window during its execution.
After the user selects the location of where to save the exported image, I'd like to popup some child window that tells the user, "Please wait...", then run the image saving code posted above, and afterwards hide the child window automatically, but I'm not having much luck accomplishing that.
For starters, the BeginInvoke code runs asynchronously, so how do I know when it has completed?
If you need to call ToImage() on the UI Thread thats fine, but it doesnt mean you have to encode the image too.
Something like this will ensure the UI stays responsive.
if (saveFileDialog.ShowDialog() == true)
{
using (var stream = saveFileDialog.OpenFile())
{
writeableBitmap.Dispatcher.BeginInvoke(delegate
{
ExtendedImage extendedImage = writeableBitmap.ToImage();
System.Threading.ThreadPool.QueueUserWorkItem(item =>
{
new PngEncoder().Encode(extendedImage, stream);
});
});
}
}

Getting Progress Updates using HttpWebRequest and TPL (Tasks)

I would like to track the progress of a download taking place on a separate thread. I know that System.Net.WebClient has a DownloadStringAsync method, but it doesn't work directly with the new TPL types (TaskFactory, Task, etc.).
Can progress be tracked using the HttpRequest and HttpResponse classes?
What's the best class for tracking progress? The less overhead the better.
Are there times when the size of the response is unknown, aka, progress can't be tracked?
What's the best way to synchronize with the UI thread whenever progress is made?
Most examples show Tasks updating the UI only after the entire task is complete. These examples use continuations taking a UI synchronization context that avoids needing to work with a Dispatcher directly.
The idea is to show a grid view (in WPF) with all the downloads with progress bars. I am going to adding new rows and updating progress bars all the time. I'm trying to avoid turning this code into a mess.
DownloadStringAsync and the other event methods work very well with TPL in .NET 4.0 (check for EAP and TPL). In general, TPL does support event async programming through the TaskCompletionSource. The Begin/EndXXX model (APM) is supported through the Task.FromAsync method. You can find a detailed description TPL and Traditional .NET Asynchronous Programming.
The ParallelExtensionExtras library has a set of WebClient extensions methods like DownloadStringTask that return a task which completes when the appropriate event is fired.
The following code will create a Task that will complete when download finishes:
public Task<string> DownloadStringTask(WebClient client,Uri uri)
{
var tcs = new TaskCompletionSource<string>();
client.DownloadStringCompleted += (o, a) => tcs.SetResult(a.Result);
client.DownloadStringAsync(uri);
return tcs.Task;
}
As for updating the UI, you can easily use the DownloadProgressChanged event to provide feedback,eg:
using (var client = new WebClient())
{
client.DownloadProgressChanged += (o, a) => Console.WriteLine("{0}",a.ProgressPercentage);
var task = DownloadStringTask(client,new Uri("http://www.stackoverflow.com"));
var write=task.ContinueWith(t => Console.WriteLine("Got {0} chars", t.Result.Length));
write.Wait();
Console.ReadKey();
}
If you use data binding to provide the progress values to your progress bars, you can just update the progress value properties. If you update the progress bars directly (not a good idea), you will have to marshal the call to the UI thread using the progress bar's dispatcher, eg. like this
void UpdateProgress(int percent)
{
if (progressBar1.CheckAccess())
progressBar1.Value = percent;
else
{
progressBar1.Dispatcher.Invoke(new Action(()=>UpdateProgress(percent)));
}
}
....
client.DownloadProgressChanged += (o, a) => UpdateProgress(a.ProgressPercentage);

Continual (rapid) update of WPF Image

There is a website that contains a single image from a webcam. Each time the site is hit, the most current image of the webcam is displayed. I want to make a real time video by hitting the site continuously.
I have searched and tried several things but cannot get it to refresh at a reasonable rate.
public MainWindow()
{
InitializeComponent();
this.picUri = "http://someurl";
this.thWatchVideo = new Thread(new ThreadStart(Watch));
_image = new BitmapImage();
_image.BeginInit();
_image.CacheOption = BitmapCacheOption.None;
_image.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
_image.CacheOption = BitmapCacheOption.OnLoad;
_image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
_image.UriSource = new Uri(this.picUri);
_image.EndInit();
this.imgVideo.Source = _image;
this.thWatchVideo.Start();
}
public void Watch()
{
while(true)
{
UpdateImage();
}
}
public void UpdateImage()
{
if (this.imgVideo.Dispatcher.CheckAccess())
{
_image = new BitmapImage();
_image.BeginInit();
_image.CacheOption = BitmapCacheOption.None;
_image.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
_image.CacheOption = BitmapCacheOption.OnLoad;
_image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
_image.UriSource = new Uri(this.picUri);
_image.EndInit();
this.imgVideo.Source = _image;
}
else
{
UpdateImageCallback del = new UpdateImageCallback(UpdateImage);
this.imgVideo.Dispatcher.Invoke(del);
}
}
Problem is, this is too slow and takes too long to refresh and the app just hangs.
I got this to work in Windows Forms with the PictureBox control but cannot get it to work in WPF. I refuse to believe that WPF is inferior to forms.
This app will always just hang (whether winforms or WPF) because you've got an infinite loop running everything it does on the UI thread. Your app hangs because you're not allowing the UI thread any time to process user input (such as resizing the window or trying to close the app).
With regard to your performance: have you tried profiling your code? I suspect that the problem is to do with you repeatedly hammering a webserver for an image, since you're never likely to get enough requests-per-second to make any kind of real-time video from static images. (There's a reason that we have video streaming codecs!)
instead of recreating whole image try to change only UriSource property.
Check out my answer to this: Showing processed images from an IP camera
Also, make sure the communication is done on a separate thread.
I suggest that the Bitmap image is a dependency object being created on a non-GUI thread. You then invoke UpdateImage on the GUI thread. Since the bitmap image dependency object wasn't created on/(owned by) the GUI thread, you get the "different thread owns it" error.
How about this as a workaround?
Copy the image temporarily to a local file location in your Watch routine.
Add a Thread.Sleep to the watch routine so that you don't hammer the CPU with the endless loop on this thread.
Use BeginInvoke instead of Invoke.
Load and update the image in the UpdateImage routine so that the image and the imgVideo objects are on the GUI thread. Update the image by reading it from your local file copy.
Without knowing the specifics of how you make Watch run on its own thread (using Background worker?) I think this approach will work for you.

WPF issue with updating

I have a strange issue. I wonder whether it's a standard behavior of the .NET components, and how to handle this.
Our app is using galasoft mvvm light. I have a form with a tree view, which is getting the data via an asynchronous call. And why that asynchronous task is running, we're showing a progress bar to the user. I'm using ObservableCollection as a collection for my tree structure. Now the problem:
This piece of code gives us the info:
public Task<ObservableCollection<FillingTreeNode>> GetTreeStructureAsync(SyncSettings settings)
{
SearchRequest request = BuildRequest();
return searchService.SearchRecordsAsync(request).ContinueWithConversion(
records => new ObservableCollection<FillingTreeNode>(records
.Select(cabinet => new FillingTreeNode
{
IsChecked = false,
DisplayName = cabinet.Fields[Fields.CabinetName].Value,
Node = cabinet.AsFillingNode(FillingNodeType.Cabinet),
NumberOfNodes = SendXmlRequest(record),
Children = new ObservableCollection<FillingTreeNode>(GetChildren (record));
}
}
This is the task extension to convert the result to some new type:
public static Task<TNew> ContinueWithConversion<TOld, TNew>(this Task<TOld> task, Func<TOld, TNew> conversionAction)
{
return task.ContinueWith(completedTask => conversionAction(task.Result));
}
Now the issue. The data is loaded from the server, the UI (the progress bar) says that the data is loaded, and only after that SendXmlRequest(record) (which is a bit long to wait) begins to work! But i expect that it's already done. The user sees nothing until those functions are finished working
Do you know what is the cause of the problem? Can that be the behavior of the Observable collection? How can i fix it?
Thank in advance.

Resources