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).
Related
I have a WPF application. A window launches and asks the user to select a launch mode, "Demo" or "Live".
If they click "Live" then an indeterminate progress bar comes up and the application will run a mixture of synchronous and asynchronous tasks which acquire data from a COM+ service while the progress bar is animated.
if the requests for data are unsuccessful a window comes up and the window with the progress bar remains on screen. The unsuccessful window shows high level failure details and gives two options: retry, quit.
the problem is that data calls are blocking the UI so even though the progressbar is set to visible, it is not actually visible until the button click event has returned (either by the data being loaded successfully or not).
If i want to run my data requests in Tasks then the UI is unblocked and the button click event is returned, the progress bar is visible and animated but if the data request fails I have no way to launch the unsucessfull window. I cannot use exceptions thrown in the data request thread to notify the main thread without awaiting the data request Task but cannot await the task as it will freeze the UI thread and not show a responsive UI while data is loaded.
I clearly have an antipattern. What is a good way to display a window when a background task fails without blocking the UI thread?
App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
launch.ShowDialog(); //this window has two buttons for live or demo launch. on selection itll show an indeterminate progressbar
loadingWindow.ShowDialog(); //this shows the window but doesnt stop exectution to the next line
}
Launch.xaml.cs
private void btnDemo_click(object sender, EventArgs e)
{
ProgressBarVisibility = Visibility.Visible; //ProgressBarVisibility prop is model bound to the progress bars visibility
// Visibility is not updated yet because the UI is currently blocked by the lines below...
try
{
LoadConfigs();
}
catch
{
Close(ExitReasons.CONFIGLOADERROR);
}
// this is where the data requests might fail and i need to
//open a new window that says which task failed (getUser or getRecord etc...) and give the
//user an option to return or close. If i make this task async then i unblock the UI but
//cannot try catch any of the errors thrown in the call chain withouth awaiting the call
//which blocks the UI....
PreCacheEHRData();
LaunchMainApp(); // if the data loads successfully the launch window is closed
//an a new window with the primary functionality is launched.
}
Thanks for your help
Call any long-running operation on a background thread by creating an awaitable Task, e.g.:
private async void btnDemo_click(object sender, EventArgs e)
{
ProgressBarVisibility = Visibility.Visible;
try
{
await Task.Run(() => LoadConfigs());
}
catch
{
Close(ExitReasons.CONFIGLOADERROR);
}
await Task.Run(() => PreCacheEHRData());
LaunchMainApp();
}
Note that the methods that run on a background thread, i.e. LoadConfigs and PreCacheEHRData in the sample code above, cannot access any UI element.
You should do the I/O work on an awaited task. Wrap try/catch block with Task.Run, and do the UI calls inside the catch block on Application's dispatcher.
make btnDemo_click async void.
private async void btnDemo_click(object sender, EventArgs e)
{
ProgressBarVisibility = Visibility.Visible;
var isDataLoadedSuccessfully = await Task.Run(() =>
{
try
{
LoadConfigs();
return true;
}
catch
{
// Application.Current.Dispatcher.Invoke(() =>
Dispatcher.Invoke(() =>
{
// here you can do UI-related calls..
Close(ExitReasons.CONFIGLOADERROR);
});
return false;
}
});
// next code will be on UI thread
ProgressBarVisibility = Visibility.Hidden;
if (isDataLoadedSuccessfully)
{
// await Task.Run(() => PreCacheEHRData());
PreCacheEHRData();
LaunchMainApp();
}
else
{
// do something
}
}
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();
});
as much as we know,
programs are executing line by line, and now I am facing issue, I have a method which is taking few seconds to execute,
I thought I could solve it by threads but I will speak about it later, now what I would like to do is next:
How could I open a window with message like "Still executing..." when that method start execution and when method is done window could close himself,
I need that because I Would like to avoid UI freezing because that long method is not in another task/thread.
Here is how it looks like:
if (e.Key == Key.Delete)
{
//Could I show window here like like "Loading please wait.."
ExecuteLongMethod();
//When this is done close this method
}
Thanks guys,
Cheers
If your ExecuteLongMethod() must be executed on the dispatcher thread for some reason, you could create a new window that launches in a separate thread and display this one during the time it takes for the long-running method to complete. Please refer to the following link for more information.
Wait screen during rendering UIElement in WPF
The other option would otherwise be to execute the long-running method on a background thread and display the loading window on the main thread.
The recommended way to do this would be to use the task parallel library (TPL) and start a task: https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx
if (e.Key == Key.Delete)
{
Window window = new Window();
window.Show();
Task.Factory.StartNew(() => ExecuteLongMethod())
.ContinueWith(task =>
{
window.Close();
},System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
An example where a top window/control is displayed with progress information and the main window is disabled:
if (e.Key == Key.Delete)
{
// create a window with a progress ring or progress bar
var window = new Window();
new Thread(() =>
{
// execute long method
ExecuteLongMethod();
// close the progress window - release control
// ensure it is done on the UI thread
System.Windows.Application.Current.Dispatcher.Invoke(() => window.Close());
}).Start();
// display progress window
window.ShowDialog();
}
Another approach would be to temporarily disable or hide any UI elements (buttons, tabs) that might interfere with the background thread and do not block the main window.
Use the await-pattern
public void YourMethod()
{
if (e.Key == Key.Delete)
{
//Could I show window here like like "Loading please wait.."
FireAndForget();
//When this is done close this method
}
}
public async void FireAndForget()
{
await Task.Run(() => { ExecuteLongMethod(); });
}
Fire and forget is an antipattern so, but sometimes u face problems where you cant await forever - like in Commands.
If you want to wait for a Result or till its finished go like this
public async void YourMethodAsync()
{
if (e.Key == Key.Delete)
{
//Could I show window here like like "Loading please wait.."
await CorrectWayAsync();
//When this is done close this method
}
}
public async Task CorrectWayAsync()
{
await Task.Run(() => { ExecuteLongMethod(); });
}
I am trying to implement a system for closing all modal and non-modal windows in a WPF application (with the exception of the main application window.) When these windows are closed, any code awaiting the result of a dialog should be abandoned.
So far, I've considered/attempted two strategies:
Close and restart the application.
Close all the windows and rely on task cancellation exceptions to abandon all code that was waiting for dialog results. (It bubbles up to the App level and then becomes handled.)
The first solution definitely gets the application to close and will suffice for automatic logout, but I am extremely uncomfortable with code that continues to execute after the dialog it was waiting for has been closed. Is there a nice way to stop the execution of that code?
The second solution has been working relatively well (calling code is aborted) but has one critical flaw: on occasion, some combination of modal and non-modal windows closing in quick succession will cause the application to lock up on a ShowDialog call. (At least, when you pause execution, that's where it ends up.) This is strange because breakpoints clearly demonstrate that the Closed event is being raised on all windows that I intend to close. The result that the end user sees is a login screen that cannot be clicked on but can be tabbed into. So strange! Attempts to dispatch the call at different priorities have been unsuccessful, but a Task.Delay for 100ms might have done the trick. (That's not a real solution, though.)
If each open popup is awaiting a TaskCompletionSource in the background, and, upon completion of the TCS, tries to use the dispatcher to invoke Close on itself, why would one (or more) of the dialogs still be blocking on ShowDialog, even after seeing the Closed event be raised? Is there a way to properly dispatch these calls to Close so they complete successfully? Do I need to be particular about the order in which the windows close?
Some pseudocode-C#-hybrid examples:
class PopupService
{
async Task<bool> ShowModalAsync(...)
{
create TaskCompletionSource, publish event with TCS in payload
await and return the TCS result
}
void ShowModal(...)
{
// method exists for historical purposes. code calling this should
// probably be made async-aware rather than relying on the blocking
// behavior of Window.ShowDialog
create TaskCompletionSource, publish event with TCS in payload
rethrow exceptions that are set on the Task after completion but do not await
}
void CloseAllWindows(...)
{
for every known TaskCompletionSource driving a popup interaction
tcs.TrySetCanceled()
}
}
class MainWindow : Window
{
void ShowModalEventHandler(...)
{
create a new PopupWindow and set the owner, content, etc.
var window = new PopupWindow(...) { ... };
...
window.ShowDialog();
}
}
class PopupWindow : Window
{
void LoadedEventHandler(...)
{
...
Task.Run(async () =>
{
try
await the task completion source
finally
Dispatcher.Invoke(Close, DispatcherPriority.Send);
});
register closing event handlers
...
}
void ClosedEventHandler(...)
{
if(we should do something with the TCS)
try set the TCS result so the popup service caller can continue
}
}
With Window.ShowDialog you create a nested Dispather message loop. With await, it's possible to "jump" on that inner loop and continue the logical execution of an async method there, e.g.:
var dialogTask = window.ShowDialogAsync();
// on the main message loop
await Task.Delay(1000);
// on the nested message loop
// ...
await dialogTask;
// expecting to be back on the main message loop
Now, if dialogTask gets completed via TaskCompletionSource before the corresponding Window.ShowDialog() call returns to the caller, the above code may still end up on the nested message loop, rather than on the main core message loop. E.g., this may happen if TaskCompletionSource.SetResult/TrySetCanceled is called inside the dialog's Window.Closed event handler, or right before/after Window.Close() call. This may create undesired reentrancy side effects, including deadlocks, too.
By looking at your pseudocode, it's hard to tell where the deadlock may be. What is worrying is that you're using Task.Run only to await a task that completes on the main UI thread, or to invoke a synchronous callback on the main UI thread from a pool thread (via Dispatcher.Invoke). You certainly shouldn't need Task.Run here.
I use the following version of ShowDialogAsync for a similar purpose. It makes sure any inner message loops started by nested ShowDialogAsync calls exit before this particular ShowDialogAsync task is complete:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
// testing ShowDialogAsync
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var modal1 = new Window { Title = "Modal 1" };
modal1.Loaded += async delegate
{
await Task.Delay(1000);
var modal2 = new Window { Title = "Modal 2" };
try
{
await modal2.ShowDialogAsync();
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal2.Title);
}
};
await Task.Delay(1000);
// close modal1 in 5s
// this would automatically close modal2
var cts = new CancellationTokenSource(5000);
try
{
await modal1.ShowDialogAsync(cts.Token);
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal1.Title);
}
}
}
/// <summary>
/// WindowExt
/// </summary>
public static class WindowExt
{
[ThreadStatic]
static CancellationToken s_currentToken = default(CancellationToken);
public static async Task<bool?> ShowDialogAsync(
this Window #this,
CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
var previousToken = s_currentToken;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(previousToken, token))
{
var currentToken = s_currentToken = cts.Token;
try
{
return await #this.Dispatcher.InvokeAsync(() =>
{
using (currentToken.Register(() =>
#this.Close(),
useSynchronizationContext: true))
{
try
{
var result = #this.ShowDialog();
currentToken.ThrowIfCancellationRequested();
return result;
}
finally
{
#this.Close();
}
}
}, DispatcherPriority.Normal, currentToken);
}
finally
{
s_currentToken = previousToken;
}
}
}
}
}
This allows you to cancel the most outer modal window via the associated CancelationToken, and that would automatically close any nested modal windows (those opened with ShowDialogAsync) and exit their corresponding message loops. So, your logical execution flow would end up on the correct outer message loop.
Note it still doesn't guarantee the correct logical order of closing multiple modal windows, if that matters. But it guarantees that the tasks returned by multiple nested ShowDialogAsync calls will get completed in the correct order.
I am not sure that this is gonna fix your issue, but in my case, I created extension methods to help mixing async code and window lifetime management. For example, you can create a ShowDialogAsync() that returns task that will complete when the window is actually closed. A CancellationToken can also be provided to close the dialog automatically if you request a cancellation.
public static class WindowExtension
{
public static Task<bool?> ShowDialogAsync(this Window window, CancellationToken cancellationToken = new CancellationToken())
{
var completionSource = new TaskCompletionSource<bool?>();
window.Dispatcher.BeginInvoke(new Action(() =>
{
var result = window.ShowDialog();
// When dialog is closed, set the result to complete the returned task. If the task is already cancelled, it will be discarded.
completionSource.TrySetResult(result);
}));
if (cancellationToken.CanBeCanceled)
{
// Gets notified when cancellation is requested so that we can close window and cancel the returned task
cancellationToken.Register(() => window.Dispatcher.BeginInvoke(new Action(() =>
{
completionSource.TrySetCanceled();
window.Close();
})));
}
return completionSource.Task;
}
}
In your UI code, you would use the ShowDialogAsync() method like the following. As you can see, when the task is cancelled, the dialog is closed and an OperationCanceledException exception is thrown stopping the flow of your code.
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
YourDialog dialog = new YourDialog();
CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromSeconds(3));
await dialog.ShowDialogAsync(source.Token);
}
catch (OperationCanceledException ex)
{
MessageBox.Show("Operation was cancelled");
}
}
This is JUST for the first part of your issue (closing the windows).
if you do not need any of the results of the windows here is some simple code just to close all but the main window.
This is executing from my main window but you can change the if statement to look for your mainwindow instead if running from alternate area.
foreach(Window item in App.Current.Windows)
{
if(item!=this)
item.Close();
}
As for other threads I am unsure although as mentioned above if you have a list of handles to the threads then you should be able to traverse that as well and kill them.
I've created a "progress/cancel" mechanism for my application whereby I can show a modal dialog while executing long-running operations, and have the dialog give some indication of progress. The dialog also has a cancel button to allow the user to cancel the operation. (Thanks to the SO community for helping me with this part).
Here's what the code looks like for running a dummy long-running operation:
public static async Task ExecuteDummyLongOperation()
{
await ExecuteWithProgressAsync(async (ct, ip) =>
{
ip.Report("Hello world!");
await TaskEx.Delay(3000);
ip.Report("Goodbye cruel world!");
await TaskEx.Delay(1000);
});
}
The parameters to the lamba are a CancellationToken and an IProgress. I'm not using the CancellationToken in this example, but the IProgress.Report method is setting the text for a label control on my progress/cancel form.
If I start this long-running operation from the button click handler on a form, it works fine. However, I've now discovered that if I start the operation from the click event handler for a ribbon button in a VSTO PowerPoint add-in, it fails at the second call to ip.Report (at the point where it tries to set the text of the label control). In this case, I get the dreaded InvalidOperationException saying that there's an invalid cross-thread operation.
There are two things that I find puzzling:
Why does the problem occur when the operation is invoked by clicking a button on the ribbon but not when invoked by clicking a button on a form?
Why does the problem occur at the second call to ip.Report but not at the first? I've not switched threads between those two calls.
You will of course want to see the rest of the code. I've tried to strip everything back to the bare bones:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncTestPowerPointAddIn
{
internal partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}
public string Progress
{
set
{
this.ProgressLabel.Text = value;
}
}
private void CancelXButton_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
public static async Task ExecuteDummyLongOperation()
{
await ExecuteWithProgressAsync(async (ct, ip) =>
{
ip.Report("Hello world!");
await TaskEx.Delay(3000);
ip.Report("Goodbye cruel world!");
await TaskEx.Delay(1000);
});
}
private static async Task ExecuteWithProgressAsync(Func<CancellationToken, IProgress<string>, Task> operation)
{
var cancellationTokenSource = new CancellationTokenSource();
var progress = new Progress<string>();
var operationTask = operation(cancellationTokenSource.Token, progress);
// Don't show the dialog unless the operation takes more than a second
const int TimeDelayMilliseconds = 1000;
var completedTask = TaskEx.WhenAny(TaskEx.Delay(TimeDelayMilliseconds), operationTask).Result;
if (completedTask == operationTask)
await operationTask;
// Show a progress form and have it automatically close when the task completes
using (var progressForm = new ProgressForm())
{
operationTask.ContinueWith(_ => { try { progressForm.Close(); } catch { } }, TaskScheduler.FromCurrentSynchronizationContext());
progress.ProgressChanged += ((o, s) => progressForm.Progress = s);
if (progressForm.ShowDialog() == DialogResult.Cancel)
cancellationTokenSource.Cancel();
}
await operationTask;
}
}
}
The form itself simply has a label (ProgressLabel) and a button (CancelXButton).
The button click event handlers for both the ribbon button and the form button simply call the ExecuteDummyLongOperation method.
EDIT: More Information
At #JamesManning's request, I put in some tracing to watch the value of the ManagedThreadId, as follows:
await ExecuteWithProgressAsync(async (ct, ip) =>
{
System.Diagnostics.Trace.TraceInformation("A:" + Thread.CurrentThread.ManagedThreadId.ToString());
ip.Report("Hello world!");
System.Diagnostics.Trace.TraceInformation("B:" + Thread.CurrentThread.ManagedThreadId.ToString());
await TaskEx.Delay(3000);
System.Diagnostics.Trace.TraceInformation("C:" + Thread.CurrentThread.ManagedThreadId.ToString());
ip.Report("Goodbye cruel world!");
System.Diagnostics.Trace.TraceInformation("D:" + Thread.CurrentThread.ManagedThreadId.ToString());
await TaskEx.Delay(1000);
System.Diagnostics.Trace.TraceInformation("E:" + Thread.CurrentThread.ManagedThreadId.ToString());
});
This was interesting. When invoked from the form, the thread ID does not change. However, when invoked from the ribbon, I get:
powerpnt.exe Information: 0 : A:1
powerpnt.exe Information: 0 : B:1
powerpnt.exe Information: 0 : C:8
powerpnt.exe Information: 0 : D:8
So, the thread ID is changing when we 'return' from that first await.
I'm also surprised that we see "D" in the trace at all, since the call immediately prior to that is where the exception occurs!
This is the expected outcome if the current thread, the one that you called ExecuteDummyLongOperation() on, doesn't have a synchronization provider. Without one, the continuation after the await operator can only run on a threadpool thread.
You can diagnose this by putting a breakpoint on the await expression. Inspect the value of System.Threading.SynchronizationContext.Current. If it is null then there is no synchronization provider and your code will fail as expected when you update the form from the wrong thread.
It isn't entirely clear to me why you don't have one. You get a provider by creating a form on the thread, before calling the method. That automatically installs a provider, an instance of the WindowsFormsSynchronizationContext class. Looks to me like you create your ProgressForm too late.