Show dialog while processing data - c#

I want to show a loading dialog when I'm doing a UI blocking task so I did this :
public void RetrievePosteInformations(string posteNumber)
{
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
//try to show dialog on UI here
RecherchePosteDialog recherchePosteDialog = new RecherchePosteDialog();
_= recherchePosteDialog.ShowAsync();
//UI blocking task
foreach(KeyValuePair<int,string> keyval in filepaths)
{
retrieveCSVInfo(keyval.Value, posteNumber, keyval.Key);
}
//after task hiding the dialog again
recherchePosteDialog.Hide();
}
But here the Dialog is not showing at all. I don't want to use an await statement because the program would keep stuck at await recherchePosteDialog.ShowAsync().
I only want to show it asynchronously and hide it when the task is finished.

You could try running it in a thread with a ManualResetEvent.
When you call
RetrieveWait.Set();
the dialog gets hidden.
Example:
private static ManualResetEvent RetrieveWait = new ManualResetEvent(false);
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
RecherchePosteDialog recherchePosteDialog = new RecherchePosteDialog();
recherchePosteDialog.ShowAsync();
RetrieveWait.WaitOne()
recherchePosteDialog.Hide();
});

Related

Running splash dialog while running heavy task in another dialog in WPF

I'm trying to implement a task as described in the caption.
The HeaviWindow performs some heavy task and so not displayed until the task finished. This time I want to display a splash screen with animated loader.
Till now I used the following construction:
private RunHeavyTask()
{
ShowSplashScreen("Please wait...");
var dialog = new HeaviWindow();
dialog.ShowDialog();
}
private void ShowSplashScreen(string str)
{
Thread newWindowThread = new Thread(new ParameterizedThreadStart(ThreadStartingPoint));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start(str);
}
private void ThreadStartingPoint(object str)
{
splash = new WaitWindow((string)str);
splash.Show();
System.Windows.Threading.Dispatcher.Run();
}
But suddenly it turned out that while running the thread all other background threads stop to work.
I've tried to use Task for this purpose:
private void ShowSplashScreen(string str)
Task.Run(() =>
{
Application.Current.Dispatcher.Invoke(delegate
{
splash = new WaitWindow((string)str);
splash.ShowDialog();
});
});
}
But splash screen not displayed until the heavy dialog finish the task. The same result if I use BackgroundWorker.
So my question - how can I display a splash dialog while running a heavy task in another one. The splash dialog use some animation so it need to be updated.
Try this:
Window splash;
private void RunHeavyTask()
{
ShowSplashScreen("Please wait...");
//simulate "heavy task" by sleeping for 5 seconds
Thread.Sleep(5000);
//close the splash window
splash.Dispatcher.BeginInvoke(() => splash.Close());
}
private void ShowSplashScreen(string str)
{
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
//pass str to your custom Window here...
splash = new Window() { Content = new ProgressBar() { IsIndeterminate = true } };
splash.Closed += (s, e) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
splash.Show();
Dispatcher.Run();
}));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
}
It will display a window with an indeterminate ProgressBar on a background thread while the thread that calls RunHeavyTask() is blocked.
It's not very clear what exactly you are trying to do. More details would be nice as it seems that your general approach needs to be optimized.
Generally you don't start the Window in a separate thread to execute heavy work (CPU bound) on the UI thread. Rather would you execute the CPU intensive task on a background thread. You can wait for this task asynchronously and then close the splash screen afterwards when the task has completed.
The following simple example shows the pattern to be used to execute the CPU bound work asynchronously on a background thread to keep the UI thread responsive, while using IProgress<T> to access the UI thread from the background thread using a delegate registered with an instance of Progress<T>.
The example assumes that the splash screen is showing some progress and for this purpose exposes a Progress property:
// Show a splash screen,
// while executing CPU bound work asynchronously on a background thread
private async Task RunHeavyTaskAsync()
{
splashScreen = new Window();
splashScreen.Show();
// Use IProgress<T> to access the UI thread via a delegate,
// which is registered using the constructor of Progress<T>
var progressReporter = new Progress<double>(value => splashScreen.Progress = value);
// Pass the IProgress<T> instance to the background thread
// and wait non-blocking (asynchronously) for the thread to complete
await Task.Run(() => DoWork(progressReporter));
// Close splash screen after 5s heavy work
splashScreen.Close();
}
// Will be executed on a background thread
private void DoWork(IProgress<double> progressReporter)
{
// Post progress from background thread (current context)
// to the UI thread (splash screen).
// IProgress<T>.Report() will execute the previously registered delegate
progressReporter.Report(50);
// Do 5s heavy work
Thread.Sleep(5000);
progressReporter.Report(100);
}

WPF close a dialog when a bool changes?

I have some simple code in a C# WPF application, using MaterialDesignInXamlToolkit that pops up a please wait type of dialog.
What I want, is for the Dialog to close automatically when a boolean value changes (from a background thread).
I have:
//create dialog
var view = new Dialog
{
DataContext = new Domain.DialogViewModel("Please wait...", true)
};
//show
var result = await DialogHost.Show(view, "RootDialog", ClosingEventHandler);
//wait
while (finished == false)
{
}
//close dialog, or enable the 'ok' button on it.
DialogHost.CloseDialogCommand.Execute(view, view); //does not work.
How can I close the Dialog from code?
I'm not familiar with the DialogHost stuff you're using, but your question is fairly straightforward otherwise. You'll have a Task that runs on a background thread and then switches back to the UI thread when it's time to close the wait dialog.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
...
void StartLengthyTask() {
var dlg = CreateWaitDialog();
dlg.Show();
// run lengthy task in a background thread
Task.Run(new Action(BackgroundThread))
// switch back to UI thread when finished
.ConfigureAwait(true)
.GetAwaiter()
// close the wait dialog
.OnCompleted(() => dlg.Close());
// logic here will execute immediately without waiting on the background task
}
void BackgroundTask() {
Thread.Sleep(5000);
}
Ah, what i needed was to override the OpenedEventHandler. Using this:
var view = new Dialog
{
DataContext = new Domain.DialogViewModel("")
};
//show the dialog
var result = await DialogHost.Show(view, "RootDialog", ExtendedOpenedEventHandler, ExtendedClosingEventHandler);
I can grab the session in a function, such as:
private void ExtendedOpenedEventHandler(object sender, DialogOpenedEventArgs eventArgs)
{
//...now, lets update the "session" with some new content!
eventArgs.Session.UpdateContent(new ProgressDialog());
//note, you can also grab the session when the dialog opens via the DialogOpenedEventHandler
//lets run a fake operation for 3 seconds then close this baby.
Task.Delay(TimeSpan.FromSeconds(3))
.ContinueWith((t, _) => eventArgs.Session.Close(false), null,
TaskScheduler.FromCurrentSynchronizationContext());
}
and then close the dialog when i wish using:
eventArgs.Session.Close(false).

Task.Run then Invoke on main thread alternative using async await ContinueWith?

The following code works perfectly. It shows the spinner on the UI, starts a task using a thread from the threadpool and runs the heavy operation, once complete, logic to hide the spinner executes on the main thread as intended.
public void LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
Task.Run(async () =>
{
var customers = await this.custService.GetCustomers();
// code truncated for clarity
Device.BeginInvokeOnMainThread(() =>
{
// Update UI to hide spinner
this.LoadingCustomers = false;
});
});
}
My question; Is there a better way to write this logic using ContinueWith/ConfigureAwait options? Using these options seems to block the UI thread. In the example below, shouldn't the UI thread continue running the UI logic (animating the spinner/user input) and then come back to complete the logic inside the ContinueWith?
public void LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
this.custService.GetCustomers().ContinueWith((t) =>
{
var customers = t.Result;
// code truncated for clarity
// Update UI to hide spinner
this.LoadingCustomers = false;
});
}
As requested in the comments, here is the code for GetCustomers. the dbContext is EntityFrameworkCore.
public async Task<List<CustomerModel>> GetCustomers()
{
return await this.dbContext.Customers.ToListAsync();
}
UPDATE
The answer by FCin is correct, however; the cause root of this seems to be with EFCore and ToListAsync, it isn't running asynchronously.
Proper way of writing such method is to use async/await from start to finish. Right now you are doing fire and forget meaning if there is exception inside Task.Run you will never know about it. You should start from an event handler. This can be whatever, mouse click, page loaded, etc.
private async void MouseEvent_Click(object sender, EventArgs args)
{
await LoadCustomers();
}
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
// We don't need Device.BeginInvokeOnMainThread, because await automatically
// goes back to calling thread when it is finished
var customers = await this.custService.GetCustomers();
this.LoadingCustomers = false;
}
There is an easy way to remember when to use Task.Run. Use Task.Run only when you do something CPU bound, such as calculating digits of PI.
EDIT: #bradley-uffner suggested to just write the following:
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
var customers = await this.custService.GetCustomers();
// code truncated for clarity
// you are still on UI thread here
this.LoadingCustomers = false;
}
How about this:
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
await Task.Run(async () =>
{
var customers = await this.custService.GetCustomers();
// code truncated for clarity
});
this.LoadingCustomers = false;
}
The code after await is executed on the current thread so it should work out of the box.

Restart WPF Application from non-UI thread

In my WPF app I need to run a quick routine on startup that checks for a new available version. If the version is available, we do the update and then would like to immediately restart the app. Since this is run before the main window appears to the user, it simply appears as though the app took a split second longer to start up.
We're using Squirrel.Windows for our updater. I've made the class below to handle checking for/applying updates.
public class UpdateVersion
{
private readonly UpdateManager _updateManager;
public Action<int> Progress;
public event Action Restart;
public UpdateVersion(string squirrelUrl)
{
_updateManager = new UpdateManager(squirrelUrl);
}
public async Task UpdateVersions()
{
using (_updateManager)
{
UpdateInfo updateInfo = await _updateManager.CheckForUpdate(progress:Progress);
if (updateInfo.CurrentlyInstalledVersion == null)
{
if (updateInfo.FutureReleaseEntry != null)
{
await _updateManager.UpdateApp(Progress);
// Job crashes here
Restart?.Invoke();
}
}
else if (updateInfo.CurrentlyInstalledVersion.Version < updateInfo.FutureReleaseEntry.Version)
{
await _updateManager.UpdateApp(Progress);
// Job crashes here
Restart?.Invoke();
}
}
}
}
Unfortunately Squirrel has made their update process async only, which means the CheckForUpdate and UpdateApp method must use await, making the entire update method asynchronous. I assign the asnyc call to a Task, then simply .Wait() for the update to finish.
The problem comes when I try to restart my app. Based on what I've read, I need to use Dispatcher.Invoke to call the restart due to the fact I am on a non-UI thread when performing the update. However, despite the code below, I still get the same error message:
The Calling thread cannot access this object because a different thread owns it
Any idea how to correctly implement Dispatcher.Invoke in order to restart the app?
// Instantiate new UpdateVersion object passing in the URL
UpdateVersion updateVersion = new UpdateVersion(System.Configuration.ConfigurationManager.AppSettings.Get("SquirrelDirectory"));
// Assign Dispatch.Invoke as Restart action delegate
updateVersion.Restart += () =>
{
Dispatcher.Invoke(() =>
{
Process.Start(ResourceAssembly.Location);
Current.Shutdown();
});
};
// This is here for debugging purposes so I know the update is occurring
updateVersion.Progress += (count) =>
{
Debug.WriteLine($"Progress.. {count}");
};
var task = Task.Run(async () => { await updateVersion.UpdateVersions(); });
task.Wait();
EDIT
Below is a screen shot of the Target attribute of the Restart action. The debugger was paused at the Restar?.Invoke line from above.
Instead of trying to convert asynchronous programming to the old event based pattern, just use it properly. You don't need events to detect when an asynchronous operation finished, nor do you need Invoke to move back to the UI thread. await takes care of both.
You could write code as simple as this:
static readonly SemanticVersion ZeroVersion = new SemanticVersion(0, 0, 0, 0);
private async void Application_Startup(object sender, StartupEventArgs e)
{
await CheckForUpdatesAsync();
}
private async Task CheckForUpdatesAsync()
{
string squirrelUrl = "...";
var updateProgress = new Progress<int>();
IProgress<int> progress = updateProgress;
//Create a splash screen that binds to progress and show it
var splash = new UpdateSplash(updateProgress);
splash.Show();
using (var updateManager = new UpdateManager(squirrelUrl))
{
//IProgress<int>.Report matches Action<i>
var info = await updateManager.CheckForUpdate(progress: progress.Report);
//Get the current and future versions.
//If missing, replace them with version Zero
var currentVersion = info.CurrentlyInstalledVersion?.Version ?? ZeroVersion;
var futureVersion = info.FutureReleaseEntry?.Version ?? ZeroVersion;
//Is there a newer version?
if (currentVersion < futureVersion)
{
await updateManager.UpdateApp(progress.Report);
Restart();
}
}
splash.Hide();
}
private void Restart()
{
Process.Start(ResourceAssembly.Location);
Current.Shutdown();
}
This is just enough code to extract to a separate class:
private async void Application_Startup(object sender, StartupEventArgs e)
{
var updater = new Updater();
await updater.CheckForUpdatesAsync(...);
}
// ...
class Updater
{
static readonly SemanticVersion ZeroVersion = new SemanticVersion(0, 0, 0, 0);
public async Task CheckForUpdatesAsync(string squirrelUrl)
{
var updateProgress = new Progress<int>();
IProgress<int> progress = updateProgress;
//Create a splash screen that binds to progress and show it
var splash = new UpdateSplash(updateProgress);
splash.Show();
using (var updateManager = new UpdateManager(squirrelUrl))
{
var updateInfo = await updateManager.CheckForUpdate(progress: progress.Report);
//Get the current and future versions. If missing, replace them with version Zero
var currentVersion = updateInfo.CurrentlyInstalledVersion?.Version ?? ZeroVersion;
var futureVersion = updateInfo.FutureReleaseEntry?.Version ?? ZeroVersion;
//Is there a newer version?
if (currentVersion < futureVersion)
{
await updateManager.UpdateApp(progress.Report);
Restart();
}
}
splash.Hide();
}
private void Restart()
{
Process.Start(Application.ResourceAssembly.Location);
Application.Current.Shutdown();
}
}
So the actual exception is somewhere in the Restart handler is trying to access the MainWindow get property from another thread based on the stack trace. This is a complete guess, but I would store the original Dispatcher in the OnStartup method and use the stored Dispatcher in the Restart event handler.
Why you are not using SplashScreen ? This SplashScreen would check for new versions, and either download updates, or start the old application.
A lovely tutorial to get you started : EASILY CREATE A WPF SPLASH SCREEN WITH STATUS UPDATES VIA MVVM

How to stop async task in c# metro application

I am developing metro application in c#, I am using async and await keywords to create make metro async operatons (downloading data etc.). I always show modal "Please wait" dialog. I would like to add "Cancel" button to this modal dialog to allow cancel the background operation.
But I am not sure how to cancel processing task... Is there any example how to do that?
This is the example of my task:
// Starts task
public void StartTask()
{
// show the modal dialog with progress
_progressDialog.IsOpen = true;
_progressDialog.CancelClick += ProgressDialog_CancelClick;
await ToWork();
_progressDialog.IsOpen = false;
}
// Task which takes some seconds
private async Task DoWork()
{
await DownloadData();
await ProcessData();
}
// Cancel task
private void CancelClick(object sender, RoutedEventArgs e)
{
// hide the modal dialog with progress
_progressDialog.IsOpen = false;
// TODO: Cancel task
GoBack(this, e);
}
You can decide to implement DownloadData and ProcessData such that they take a CancellationToken and pass that to them when you need to cancel.
public Task DownloadData(CancellationToken tok)
{
tok.ThrowIfCancellationRequested();//check that it hasn't been cancelled.
//while doing your task check
if (tok.IsCancellationRequested)
{
// whatever you need to clean up.
tok.ThrowIfCancellationRequested();
}
}
For usage you can create a CancellationTokenSource and pass the token to the method.
var source = new CancellationTokenSource();
await DownloadData(source.Token);
When you need to cancel you can call Cancel() on the source
source.Cancel();

Categories