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);
}
Related
I am currently trying to implement a splash screen. I have taken this tutorial as a starting point.
OnStartup in my App.xaml.cs looks like this:
protected override void OnStartup(StartupEventArgs e)
{
//initialize the splash screen and set it as the application main window
splashScreen = new MySplashScreen();
this.MainWindow = splashScreen;
splashScreen.Show();
//in order to ensure the UI stays responsive, we need to
//do the work on a different thread
Task.Factory.StartNew(() =>
{
//we need to do the work in batches so that we can report progress
for (int i = 1; i <= 100; i++)
{
//simulate a part of work being done
System.Threading.Thread.Sleep(30);
//because we're not on the UI thread, we need to use the Dispatcher
//associated with the splash screen to update the progress bar
splashScreen.Dispatcher.Invoke(() => splashScreen.Progress = i);
splashScreen.Dispatcher.Invoke(() => splashScreen.MyText = i.ToString());
}
//once we're done we need to use the Dispatcher
//to create and show the main window
this.Dispatcher.Invoke(() =>
{
//initialize the main window, set it as the application main window
//and close the splash screen
var mainWindow = new MainWindow();
this.MainWindow = mainWindow;
mainWindow.Show();
splashScreen.Close();
});
});
}
This works perfectly. The splash screen is called and progress (ProgressBar) is incremented up to 100.
Now I want to write progress to the splash screen not only from OnStartup, but also from the constructor of my MainWindow.
My MainWindow constructor:
public MainWindow()
{
InitializeComponent();
((App)Application.Current).splashScreen.Dispatcher.Invoke(() => ((App)Application.Current).splashScreen.MyText = "From MainWindow");
// do some stuff that takes a few seconds...
}
This is not working as expected. The text "From MainWindow" is updated in the text box of the splash screen only after the constructor has been called completely. Not as expected before "do some stuff that takes a few seconds..." is executed.
What's my mistake? Is this even as possible as I thought?
The Dispatcher is already busy creating the MainWindow as you invoked the constructor using Dispatcher.Invoke. Then in the constructor of MainWindow you invoked the Dispatcher again. Dispatcher.Invoke effectively enqueues the delegate into the dispatcher queue. Once the first delegate ran to completion the next one (in this case the one from inside the constructor of MainWindow) is dequeued and executed (always with respect to the given DispatcherPriority). That's why you have to wait until the constructor completes i.e. the first delegated has completed.
I highly recommend to use Progress<T> which is the recommended way of progress reporting starting from .NET 4.5 (Async in 4.5: Enabling Progress and Cancellation in Async APIs). Its constructor captures the current SynchronizationContext and executes the report callback on it. Since the instance of Progress<T> is created on the UI thread the callback will execute automatically on the proper thread so that no Dispatcher is required anymore. This will solve your problem. In addition when used in an asynchronous context the progress reporting can make use of cancellation too.
I also recommend to use async/ await to control the flow. The goal is to create the instance of MainWindow on the UI thread.
Also always avoid using Thread.Sleep as it will block the thread. In this case the UI thread which will get unresponsive and frozen as a result. Use the asynchronous (non-blocking) await Task.Delay instead. As a rule of thumb replace all references to Thread with Task, i.e. the Task Parallel Library is the preferred way to go (Task-based asynchronous programming).
I refactored your code accordingly:
App.xaml.cs
private SplashScreen { get; set; }
protected override async void OnStartup(StartupEventArgs e)
{
// Initialize the splash screen.
// The first Window shown becomes automatically the Application.Current.MainWindow
this.SplashScreen = new MySplashScreen();
this.SplashScreen.Show();
// Create a Progress<T> instance which automatically
// captures the current SynchronizationContext (UI thread)
// which makes the Dispatcher obsolete for reporting the progress to the UI.
// Pass a report (UI update) callback to the Progress<T> constructor,
// which will execute automatically on the UI thread.
// Because of the generic parameter which is in this case of type ValueTuple (C# 7),
// 'System.ValueTuple' is required to be referenced (use NuGet Package Manager to install).
// Alternatively replace the tuple with an arg class.
var progressReporter = new Progress<(int Value, string Message)>(ReportProgress);
// Wait asynchronously for the background task to complete
await DoWorkAsync(progressReporter);
// Override the Application.Current.MainWindow instance.
this.MainWindow = new MainWindow();
// Asynchronously wait until MainWindow is initialized
// Pass the Progress<T> instance to the method,
// so that MainWindow can report progress too
await this.MainWindow.InitializeAsync(progressReporter);
this.SplashScreen.Close();
this.MainWindow.Show();
}
private async Task DoWorkAsync(IProgress<(int Value, string Message)> progressReporter)
{
// In order to ensure the UI stays responsive, we need to
// do the work on a different thread
await Task.Run(
async () =>
{
// We need to do the work in batches so that we can report progress
for (int i = 1; i <= 100; i++)
{
// Simulate a part of work being done
await Task.Delay(30);
progressReporter.Report((i, i.ToString()));
}
});
}
// The progress report callback which is automatically invoked on the UI thread.
// Requires 'System.ValueTuple' to be referenced (see NuGet)
private void ReportProgress((int Value, string Message) progress)
{
this.SplashScreen.Progress = progress.Value;
this.SplashScreen.MyText = progress.Message;
}
MainWindow.xaml.cs
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
public async Task InitializeAsync(IProgress<(int Value, string Message)> progressReporter)
{
await Task.Run(
() =>
{
progressReporter.Report((100, "From MainWindow"));
// Run the initialization routine that takes a few seconds
}
}
}
I have a c++ application and use clr to call the below c# method. Everything is going good except for one thing. As wpf window needs STA thread, I'm trying to create a new thread with STA state and start it. This makes the wpf window modeless, even if the window is started with show dialog. So I tried using thread.join() to make the caller thread to wait until the thread completes or window closes. using thread.join() shows server busy window after a few seconds. (I'm not doing any operation related to the internet in my application).
How to make the caller thread to wait until the window closes? or How to get rid of Server busy window?
void ShowWindow()
{
_ownerHandle = Process.GetCurrentProcess().MainWindowHandle;
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
MyWpfWindow window = new MyWpfWindow();
MyWpfWindowViewModel vm = new MyWpfWindowViewModel();
WindowInteropHelper helper = new WindowInteropHelper(window);
helper.Owner = _ownerHandle;
window.DataContext = vm;
window.ShowDialog();
}));
// set the apartment state this will only invoke the WPF window
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
// start the thread
newWindowThread.Start();
//waits this thread here untill the newWindowThread execution completes.
newWindowThread.Join();
}
Also, I tried with a while loop as below to block the current main thread.
while(!windowShown)
{
}
in the place of newWindowThread.join(). After using this, my window is not visible.
I'm getting a server busy window as below
Calling Join() blocks the current thread. If you want to wait asynchronously, you could use a SemaphoreSlim:
static async Task ShowWindowAsync()
{
SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
_ownerHandle = Process.GetCurrentProcess().MainWindowHandle;
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
MyWpfWindow window = new MyWpfWindow();
MyWpfWindowViewModel vm = new MyWpfWindowViewModel();
WindowInteropHelper helper = new WindowInteropHelper(window);
helper.Owner = _ownerHandle;
window.DataContext = vm;
window.Closed += (s, e) =>
{
semaphore.Release();
semaphore.Dispose();
};
window.ShowDialog();
}));
// set the apartment state this will only invoke the WPF window
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
// start the thread
newWindowThread.Start();
//waits this thread here untill the newWindowThread execution completes.
await semaphore.WaitAsync();
}
I'm new with C# and I want to show a loading window with an indeterminate progressbar during the execution of a Dispatcher.BeginInvoke, I try something like this:
progressbarwin = new Windows1();
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ContextIdle, (Action)delegate
{
//Do something
progressbarwin.Close();
});
progressbarwin.ShowDialog();
The window is showed (before the delegate) but the progressbar is blocked. What can I do?
#Sinatr
I tried to open the Windows from a new thread UI:
Thread loadT = new Thread(new ThreadStart(() =>
{
Loading progressbarwin = new Loading();
progressbarwin.ShowDialog();
}));
// Set the apartment state
loadT.SetApartmentState(ApartmentState.STA);
// Make the thread a background thread
loadT.IsBackground = true;
// Start the thread
loadT.Start();
//Do something
loadT.Abort();
but it gives me a cross thread exception when i create the window (new Loading())
#Mitra M
It's the same :(
I have following Code:
Thread thread = new Thread(new ThreadStart(CreateSplashScrn));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
OpenSplashScrn();
ChangeSplashScrnMessageText("String");
public void CreateSplashScrn()
{
splash = new SplashScreen(this);
System.Windows.Threading.Dispatcher.Run();
}
public void OpenSplashScrn()
{
splash.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(() => { splash.Show(); }));
}
public void ChangeSplashScrnMessageText(string messageText)
{
splash.messageLabel.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(() => { splash.messageLabel.Content = messageText; }));
}
However, this returns a Null Reference Exception at OpenSplashScrn().
How can I open this in another thread and change the Label content?
Is this possible over a Task?
You should NOT open splach screen in background thread and perform the long running initialization in UI thread.
You should open splash screen in UI thread and perform the long running initialization in non UI thread.
var splash = new SplashScreen(this);
splash.Show();
Thread thread = new Thread(new ThreadStart(Initialize));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
public void Initialize()
{
//move your long running logic from your app here..
ChangeSplashScrnMessageText("Initialization Started");
Thread.Sleep(1000);
ChangeSplashScrnMessageText("Initialize finished");
}
public void ChangeSplashScrnMessageText(string messageText)
{
splash.messageLabel.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(() => { splash.messageLabel.Content = messageText; }));
}
EDIT: Why you should not open Splash Screen in another thread?
Because it complicates things a log and in 99% there is no reason to do so. You can have multiple windows running in single thread and still perform some long running task in the background.
I guess, that in your main window you are trying to perform the long running task in UI thread. Just move it to background thread...
I am able to open a new window in a new thread by the following code.
The following code is from MainWindow.xaml.cs
private void buttonStartStop_Click(object sender, RoutedEventArgs e)
{
Test test = new Test();
Thread newWindowThread = new Thread(new ThreadStart(test.start));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
}
and the following from test.start()
public void start()
{
OutputWindow outputwindow = new OutputWindow();
outputwindow.Show();
Output.print("Begin");
System.Windows.Threading.Dispatcher.Run();
Output.print("FINAL");
System.Windows.Threading.Dispatcher.Run();
}
And the following is from the Output class
public static void print(String str)
{
Dispatcher uiDispatcher = OutputWindow.myOutputWindow.Dispatcher;
uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.AppendText(str + "\n"); }));
uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.ScrollToLine(OutputWindow.myOutputWindow.textBoxOutput.LineCount - 1); }));
}
public static void printOnSameLine(String str)
{
Dispatcher uiDispatcher = OutputWindow.myOutputWindow.Dispatcher;
uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.AppendText(str); }));
uiDispatcher.BeginInvoke(new Action(delegate() { OutputWindow.myOutputWindow.textBoxOutput.ScrollToLine(OutputWindow.myOutputWindow.textBoxOutput.LineCount - 1); }));
}
"Begin" Does get printed in the textbox but "FINAL" does not, I want the start method in Test class to update the textbox in outputwindow through out the program. What is the best way to do this?
Thank you in advance
I'm not sure what are you trying to do. It is normal that FINAL does not print because you called System.Windows.Threading.Dispatcher.Run(). This method keeps thread alive and listens for events. You can look at it like if you have while(true){} inside the Run method. Method will continue to run until Dispatcher is shutdown. You should keep background thread alive and call your static methods from another thread when you need to set a message. Here's an example:
// reference to window in another thread
Window outputWindow = null;
Thread thread = new Thread(() =>
{
// another thread
outputWindow = new Window();
outputWindow.Show();
// run event loop
System.Windows.Threading.Dispatcher.Run();
}) { ApartmentState = ApartmentState.STA, IsBackground = true };
thread.Start();
while (outputWindow == null)
{
// wait until the window in another thread has been created
Thread.Sleep(100);
}
// simulate process
for (int i = 0; i < 10; i++)
{
outputWindow.Dispatcher.BeginInvoke((Action)(() => { outputWindow.Title = i.ToString(); }), System.Windows.Threading.DispatcherPriority.Normal);
Thread.Sleep(500); // simulate some hard work so we can see the change on another window's title
}
// close the window or shutdown dispatcher or abort the thread...
thread.Abort();
EDIT:
This could be quick & dirty generic solution. DoSomeHardWork creates another GUI thread for wait window which displays progress information. This window creates work thread which actually does the work. Work is implemented in method Action. 1st argument is wait window so you can change it from work thread. Of course, in the real world you should go through interface and not directly to window implementation but this is just an example. 2nd argument is object so you can pass whatever you need to the work thread. If you need more arguments pass object[] or modify method signature. In this example I simulate hard work with counter and sleep. You can execute this code on button click multiple times and you will see all wait windows counting their own counter without freezing. Here is the code:
public static void DoSomeHardWork(Action<Window, object> toDo, object actionParams)
{
Thread windowThread = new Thread(() =>
{
Window waitWindow = new Window();
waitWindow.Loaded += (s, e) =>
{
Thread workThread = new Thread(() =>
{
// Run work method in work thread passing the
// wait window as parameter
toDo(waitWindow, actionParams);
}) { IsBackground = true };
// Start the work thread.
workThread.Start();
};
waitWindow.Show();
Dispatcher.Run();
}) { ApartmentState = ApartmentState.STA, IsBackground = true };
// Start the wait window thread.
// When window loads, it will create work thread and start it.
windowThread.Start();
}
private void MyWork(Window waitWindow, object parameters)
{
for (int i = 0; i < 10; i++)
{
// Report progress to user through wait window.
waitWindow.Dispatcher.BeginInvoke((Action)(() => waitWindow.Title = string.Format("{0}: {1}", parameters, i)), DispatcherPriority.Normal);
// Simulate long work.
Thread.Sleep(500);
}
// The work is done. Shutdown the wait window dispather.
// This will stop listening for events and will eventualy terminate
// the wait window thread.
waitWindow.Dispatcher.InvokeShutdown();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
DoSomeHardWork(MyWork, DateTime.Now);
}
Ideally the thread(UI thread) that creates the UI elements own's the elements too. With dispatcher all you are doing is that you are pushing the non-UI related processing into a background thread. Once the background process is completed, the result again will be pushed back to the main UI thread. For sample example check out : http://weblogs.asp.net/pawanmishra/archive/2010/06/06/understanding-dispatcher-in-wpf.aspx