Using async / wait, UI Freezes after setting content - c#

Got some problem and cant't get why it appears.
I'm using .net 4.5 / C# and I try to set Content to a ContentControl after an async function succeeded.
The main focus of what i want to to in that part of the programm is to switch between an own WPF Loading animation (Usercontrol IsLoading) and a PDF Content (Usercontrol PDFDokument). The PDF is internal loaded & rendered inside the "PDFDokument" and works already very well.
If more information is needed, every asking is welcome.
For you to know i would say i'm still at a beginning level of developing. (the first of three years :) )
public ucPDFDocument PDFDokument = new ucPDFDocument();
public ucLoading IsLoading = new ucLoading();
protected async void lstSuchergebnis_DoubleClickItem(object sender, MouseButtonEventArgs e)
{
var tempAkte = ((ListViewItem)sender).Content as Akten;
tbctrlResultPanel.SelectedIndex = 1;
PDFDokument.IsDataChangeAllowed(false);
contSwapControls.Content = IsLoading;
await PDF(tempAkte);
contSwapControls.Content = PDFDokument; **<-- after executing this line, the ui freezes**
}
private Task PDF(Akten paramAkte)
{
Akten _tempAkte = paramAkte;
return Task.Run(() => { PDFDokument.LoadPDFDokument(_tempAkte.akt_ID, ref _DaKsManger); });
}
I tried different ways of using that async loading, but nothing solved that problem.
Hope someone got an idea how to solve that :)
Thanks a lot!!!

The only thing that is async is PDF(...), which you await. Setting the content is not being executed in an async manner. The content you are setting happens on the UI thread. (which is the only way to do so, since you are modifying the UI, which can only happen on the thread it is created on)

Related

Unity - Make a Sync Method that waits for user input

I have basic knowledge of C# with WinForms and WPF. But I am new to Unity.
I want to make a series of Message Boxes those appear one after another(once user close current than next box will show). It is very easy to use MessageBox.Show("Hello World"); in WinForms.
void myFun(){
MessageBox.Show("Hello World1");//First Box
//Some Code
MessageBox.Show("Hello World2");//Another
MessageBox.Show("Hello World3");//Finally Results
}
In WinForms(or WPF) Code after MessageBox.Show(); will not Executed before we Give input to close.
I want to make same MessageBox in unity with GameObject where code will not execute before GameObject is SetActive(False); by user command(mouse Click on Background or Yes/No Buttons).
Thanks in Advance
sorry for bad English
One of the best approaches becoming popular in Unity the last years is an Async approach. So I highly recommend you to learn this to have even more powerful tool than Coroutines and to avoid Callback Hell in case of Action usage.
Let's implement it this Async approach together.
Firstly, we need that MessageBox implementation. Let's say it has 2 buttons: Confirmation and Cancellation. Here's it:
public MessageBox : MonoBehaviour
{
[SerializeField]
private Button _confirmationButton;
[SerializeField]
private Button _cancelButton;
private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();
public void OnConfirmationButtonClick()
{
// we're passing `true` if user clicks `Confirm`
_tcs.SetResult(true);
}
public void OnCancellationButtonClick()
{
// we're passing `false` if user clicks `Cancel`
_tcs.SetResult(false);
}
public async Task<bool> ShowAsync()
{
gameObject.SetActive(true);
// recreate an instance to not use the `SetResult` value of previous showing
_tcs = new TaskCompletionSource<bool>();
// the execution stops here ASYNCHRONOUSLY, so the UI thread is not blocked.
// It just awaits until we set any result to `_tcs`
await _tcs.Task;
gameObject.SetActive(false);
}
}
And now we can show a few message boxes and await for their input. Moreover, we may know what exactly button was clicked: Confirmation or Cancellation:
public async Task YourOwnMethodAsync()
{
// Let's assume you've instantiated 3 message boxes already and have their references
var resultOfFirstMessageBox = await _messageBox1.ShowAsync();
Debug.Log($"Showing the first message box shown. Result: {resultOfFirstMessageBox}");
var resultOfSecondMessageBox = await _messageBox2.ShowAsync();
Debug.Log($"Showing the second message box shown. Result: {resultOfSecondMessageBox}");
var resultOfThirdMessageBox = await _messageBox3.ShowAsync();
Debug.Log($"Showing the third message box shown. Result: {resultOfThirdMessageBox}");
}
If you need even more detailed description, just let me know.

Load heavy user control with laoding animation without freezing app

so after more than a week of trying to solve it on my own I officially give up and turn to your help. Basically, it should not be so complicated so I have no idea why it does not work. I have a WPF app which contains a Main Window called surprise surpise...: Main_Window.
That window contain a user control called 'pageTransitionControl' that change its content according to what the client want to see. the 'pageTransitionControl' is there to support multiple animations and so on... Anyway, among all of the user controls, i have a preety havy uc called ucBanks. before it shows, the ucBanks load a lot of data, manipulating it and display it on a very beautiful and smart charts. the problem is it takes some time to load it, approximately 6-7 seconds so i need the UI to show 'Loading' animation during that time (another user control called 'ucSpinner').
I'm Trying to load the ucBanks on a different thread to avoid freezing the application and it works great: the ucSpinner is showed immidiatlly and the ucBanks is loading on the background but when i change the content of the 'pageTransitionControl' i get this error:
"The calling thread cannot access this object because a different thread owns it".
I think i tried basically everything but i must missing somthing or doing somthing wrong.
This is where it all start, the btn_click event that load ucBanks:
ShowSpinner();
Thread.Sleep(100);
Thread newThread = new Thread(new ThreadStart(LoadUc));
newThread.SetApartmentState(ApartmentState.STA);
newThread.IsBackground = true;
newThread.Start();
This is the ShowSpinner method:
private void ShowSpinner()
{
ucSpinner.Opacity = 1;
}
and this is the LoadUc method:
private void LoadUc()
{
ucOsh ucOshx = new ucOsh();
Utils.LoadUc(ucOshx, null, PageTransitions.PageTransitionType.GrowAndFade, true, this, null, true);
}
With the LoadUc i called static class called 'Utils' holding the 'LoadUc' method:
public static void LoadUc(System.Windows.Controls.UserControl ucParent, System.Windows.Controls.UserControl ucChild, PageTransitions.PageTransitionType tranType, bool removeChildrens = true, System.Windows.Window w = null, List<Plist.Plist> lst = null, bool hideMenu = false)
{
MainWindow win = null;
if (w != null) { win = (MainWindow)w; }
else { win = (MainWindow)System.Windows.Window.GetWindow(ucChild); }
win.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.ContextIdle, (System.Action)delegate
{
win.pageTransitionControl.TransitionType = tranType;
win.pageTransitionControl.PARAMS = lst;
win.pageTransitionControl.Tag = ucParent.ToString();
win.pageTransitionControl.pages.Push(ucParent);
win.pageTransitionControl.Content = ucParent; ----------->>>>This is where i get the error!!!
});
}
I understand that the main window is locked inside another thread but i cant see any other option to load it without freezing the entire app.
Does anyone have a suloution to my problem? SA :-) ?
What I have tried:
i tried working with background-worker, i chaned all of the settings of the dispatcher, loaded the user control inside and outside the threads...

ShowMetroDialogAsyncdoesn't show the dialog when waiting for the task

I tried to implement a DialogManager like described here. I'm not using caliburn, so I refactored a bit, and also it's no longer SimpleDialog but CustomDialog, but there weren't big changes.
So now when I click a button that calls DialogManager.ShowDialog and wait for the resulting task with task.Wait() the application just freezes (as aspected for wait) but without showing the dialog. I tried to debug, but it works alright until the line
await Application.Current.Windows.OfType<MetroWindow>().First().ShowMetroDialogAsync(dialog);
. dialog is a valid BaseMetroDialog and i get the right window from the Application.Cur... call. It also happens when I'm just calling ShowMessageAsync, so the problem seems to be in the connection of the call and the waiting for the task. Is there no way to really block the following execution (forcing the dialog to be modal)?
If you need additional information please comment, I'll extend the question, but right now I don't know what to show besides the code already linked at the beginning.
You mention task.Wait(). If you started a task to show the dialog, I'm afraid you can't do that. The GUI is single threaded. You can still await the result, just has to be initiated from the main thread, not a task.
This is all the gimmicks I had to do in order to get this to work without having to use async/await:
using SysThread = System.Threading;
using WpfThread = System.Windows.Threading;
using SysTasks = System.Threading.Tasks;
using MahCtl = MahApps.Metro.Controls;
using MahDlg = MahApps.Metro.Controls.Dialogs;
using Wpf = System.Windows;
.
.
.
private SysThread.CancellationTokenSource tokenSource;
.
.
.
MahCtl.MetroWindow parentMetroWindow = Wpf.Application.Current.Windows.OfType<MahCtl.MetroWindow>().First();
var metroDialogSettings = new MahDlg.MetroDialogSettings();
metroDialogSettings.OwnerCanCloseWithDialog = true; //does not appear to have any effect
metroDialogSettings.AnimateHide = false;
metroDialogSettings.AnimateShow = false;
using( tokenSource = new SysThread.CancellationTokenSource() )
{
metroDialogSettings.CancellationToken = tokenSource.Token;
SysTasks.Task<MahDlg.MessageDialogResult> task = MahDlg.DialogManager.ShowMessageAsync( parentMetroWindow, title, message, mahStyle, metroDialogSettings );
parentMetroWindow.Closing += onMainWindowClosing;
while( !(task.IsCompleted || task.IsCanceled || task.IsFaulted) )
Wpf.Application.Current.Dispatcher.Invoke( WpfThread.DispatcherPriority.Background, new Action( delegate { } ) );
parentMetroWindow.Closing -= onMainWindowClosing;
return task.Result;
}
.
.
.
private void onMainWindowClosing( object sender, SysCompMod.CancelEventArgs cancelEventArgs )
{
tokenSource.Cancel();
}
The extra handler for the Closing event of the main window is necessary in order to handle the situation where the user attempts to close the main window of your application via the task bar, and while a modal dialog box is open, because OwnerCanCloseWithDialog does not seem to have any effect, or it was never meant to do what the documentation tricked me into believing that it was perhaps meant to do.

Kinect Frame Arrived Asynchronous

I am looking for some help with my MultiSourceFrameArrived event in the Kinect v2 SDK.
The following is the method in question:
private async void _reader_MultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
{
MultiSourceFrame multiSourceFrame = e.FrameReference.AcquireFrame();
using (var colorFrame = multiSourceFrame.ColorFrameReference.AcquireFrame())
{
if (colorFrame != null)
{
_writeableBitmap.Lock();
colorFrame.CopyConvertedFrameDataToIntPtr(
_writeableBitmap.BackBuffer,
(uint)(_colorFrameDescription.Width * _colorFrameDescription.Height * _colorFrameDescription.BytesPerPixel),
ColorImageFormat.Bgra);
_writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, _writeableBitmap.PixelWidth, _writeableBitmap.PixelHeight));
_writeableBitmap.Unlock();
reflectionOverlayControl.ReflectionImageSource = _writeableBitmap;
}
}
using (var bodyFrame = multiSourceFrame.BodyFrameReference.AcquireFrame())
{
if (bodyFrame != null)
{
Body body = JointHelpers.FindClosestBody(bodyFrame);
if (body != null)
{
if (body.IsTracked)
{
Dictionary<BodyComponentType, BodyComponent> bodyComponentDictionary = BuildBodyComponentDictionary(body);
foreach (BodyComponent bodyComponent in bodyComponentDictionary.Values.OrderBy(x => x.BodyComponentType))
{
bodyComponent.Generate(_writeableBitmap, _coordinateMapper, FrameType.Color, 25);
if (!_isRunningFiltering)
{
_isRunningFiltering = true;
try
{
await Task.Run(() =>
{
bodyComponent.RunFunFiltering();
});
}
finally
{
_isRunningFiltering = false;
}
}
}
reflectionOverlayControl.UpdateValues(
bodyComponentDictionary,
GetFullBodyComponent(body));
}
}
}
}
}
Now, allow me to explain:
The method runs when a particular kind of frame arrives from the Kinect, this is acquired and I can extract the ColorFrame and BodyFrame out of it in the using blocks.
The first "using" block turns the ColorFrame into a WPF WriteableBitmap (declared in the constructor) and sets a user control's ReflectionImageSource set equal to this WriteableBitmap. If this were the only using block, I would see a very smooth feed on the screen!
The second BodyFrame using determines the closest body, if it is tracked and then creates a Dictionary populated with a persons BodyComponents (hands, feet, head etc.)
The foreach loop here runs the "Generate" function on each BodyComponent, which sets a few of it's properties. For example, it sets an EncompassingRectangle property which is an Int32Rect object designed to encompass the component.
The next bit is where I need help!
The method RunFunFiltering is a heavily intensive processing method which, when run, would create a blocking statement that freezes up my UI. This would have the effect of making my color frame video feed very jumpy! This RunFunFiltering method needs to set some of the BodyComponent class's properties, such as the colour that the rectangle should be displayed, the number of white pixels in it's ReflectionImageSource and to set another writeable bitmap with the part of the first ReflectionImageSource which is contained in the rectangle.
Since this object is now complete, with all properties set (and this has been done for each of the BodyComponent's in the dictionary) I run an UpdateValues method on the view, which displays the interesting stuff in the BodyComponent class on the screen for me.
Following some advice from #sstan in this post: Async Await to Keep Event Firing
I threw in a Task.Run() block. However, this doesn't seem to be releasing my UI and I still see a jumpy image. The weird thing is in that timer example, that it works perfectly! I'm at a bit of a loss here to know what to do.
I'm a bit of a beginner with asynchronous functions but I would really like to understand your solutions. If you can provide an explanation with your code I'd be extremely grateful!
Update
I have been able to identify that the using statement which acquires the frame blocks the UI when it is placed outside of the Task.Run call.
I can't just make the whole BodyFrame using block run asynchronously because I need the first "Generate" function to always happen and not be part of the heavy processing thread. Two using blocks seems inelegant and is rather pushing my question under the carpet...
From your comment I understand the following:
You have an async function that is called when a frame arrives
If no RunFunFiltering task is running start one
If such a task is running, don't start a new one
If RunFunFiltering is finished Process the result
.
Task taskFunFiltering = null;
private async Task ProcessFrame(...)
{ // a new frame is arrived
DoSomeProcessing(...);
// only start a new run fun filtering if previous one is finished
if (taskFunFiltering == null || taskFunFiltering.IsCompleted)
{ // start a new fun filtering
// don't wait for the result
taskFunFiltering = Task.Run( () => ...);
}
}
private async Task RunFunFiltering(...)
{
// do the filtering and wait until finished
var filterResult = await DoFiltering(...);
DisplayResult(filterResult);
}

Caliburn.Micro Go Back called in On Activate not working in WinRT

This question is specific to Windows Phone 8.1 (WinRT); it may also be applicable Windows 8.1. I am using Caliburn.Micro 2.0.1
In my ViewModel's OnActivate I check whether an item is a database, if it isn't, I want to navigate back to the previous page.
The simplist solution will be just to call GoBack in the OnActivate method (this works in Windows Phone 8.0):
INavigationService _navigationService;
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
_navigationService.GoBack()
}
}
To navigate to the view model I call:
_navigationService.NavigateToViewModel<MyViewModel>(_param);
But it does not work, it ignores the GoBack call and stays on the page which I do not want to view.
When stepping through the code you can see that the GoBack code is called inside the NavigateToViewModel method; I expect this is the reason why it does not work (something to do with a queuing issue maybe?).
I have a very "hacky" solution that involves a timer (that works), but I really despise it since it is prone to threading issues and has the possibility of being called during the NavigateToViewModel call (if it takes long to finish), which will then again not work:
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
DispatcherTimer navigateBackTimer = new DispatcherTimer();
navigateBackTimer.Interval = TimeSpan.FromMilliseconds(300);
navigateBackTimer.Tick += GoBackAfterNavigation;
navigateBackTimer.Start();
}
}
public void GoBackAfterNavigation(object sender, object e)
{
_navigationService.GoBack();
(sender as DispatcherTimer).Stop();
}
Is there a better way to navigate back? Why doesn't the GoBack work in OnActivate? Is there a way to get it to work in OnActivate?
You can use
Execute.OnUIThreadAsync(() => /* navigationCode */);
instead of a timer to queue the action immediately after the processing of the current stack has finished.

Categories

Resources