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
CodePudding user response:
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)
{
PreCacheEHRData();
LaunchMainApp();
}
else
{
// do something
}
}
CodePudding user response:
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.