I'm having an issue closing a program when there is a large amount of dialogs open. I was able to reproduce the bug using the following functions. The parent form on which this runs on is created using Application.Run(mainform).
When button1 is clicked, it creates a very basic form that opens another dialog of itself when "ok" is pressed. It then initiates the OnShutdown() function. This waits 15 seconds, then attempts to close all windows and exit the program. When more than 6 dummy windows are open, the function closes all the dummy windows but not the main window.
The steps for me reproducing the problem is:
Click button1
Close created heavy dialog
Click on on the msgbox
Click on button1 again
On the new heavy dialog, I press Ok to open another heavy dialog
Continue to open more heavy dialog, until 6-10 are open concurrently
Once the 15 seconds are up, all the dialogs close, but the parent form remains open, when it should be closed.
private void button1_Click(object sender,EventArgs e) {
BasicTemplate heavy = new BasicTemplate();
heavy.ShowDialog();
OnShutdown();
}
private void OnShutdown() {
timerSignals.Enabled=false;//quit receiving signals.
string msg = "";
msg+=Process.GetCurrentProcess().ProcessName+" ";
msg+=Lan.g(this,"will shut down in 15 seconds. Quickly click OK on any open windows with unsaved data.");
MsgBoxCopyPaste msgbox = new MsgBoxCopyPaste(msg);
msgbox.Size=new Size(300,300);
msgbox.TopMost=true;
msgbox.Show();
ODThread killThread = new ODThread((o) => {
Thread.Sleep(15000);//15 seconds
//Also happens with BeginInvoke()
Invoke((Action)(() => { CloseOpenForms(true); }));
Thread.Sleep(1000);//1 second
Invoke((Action)Application.Exit);
});
killThread.Start(true);
return;
}
public void Start(bool isAutoCleanup) {
_isAutoCleanup=isAutoCleanup;
if(_thread.IsAlive) {
return;//The thread is already running.
}
if(_hasQuit) {
return;//The thread has finished.
}
_dateTimeStart=DateTime.Now;
_thread.Start();
}
//Code for "OK" button on the heavy dialog created above
private void butOK_Click(object sender,EventArgs e) {
BasicTemplate formp = new BasicTemplate();
formp.ShowDialog();
}
private bool CloseOpenForms(bool isForceClose) {
for(int f=Application.OpenForms.Count-1;f>=0;f--) { //Count backwards to avoid out of bounds
if(Application.OpenForms[f]==this) {
continue;
}
Form openForm=Application.OpenForms[f];//Copy so we have a reference to it after we close it.
openForm.Hide();
if(isForceClose) {
openForm.Dispose();
}
else {
openForm.Close();//Attempt to close the form
if(openForm.IsDisposed==false) {
openForm.Show();//Show that form again
return false;
}
}
}
return true;
}
When line 3's "heavy.ShowDialog()" is changed to "heavy.Show()", the holdup no longer takes place. When multiple heavy dialogs are opened using a different button, the issue no longer takes place (up to ~20 new dialogs).
The shutdown sequence is run as a thread to allow the user to save any changes they have made before the program's database updates. I'm not sure if what I've implemented correctly shows the bug, but it at least produces similar events.
Related
Is it possible to stop an ongoing process with a button click in Windows form application?
For example, let's say there are 2 buttons, "START" and "STOP"
When you press "START", it will start an infinite loop, printing numbers from 1 to infinity.
When I press "STOP", the process should stop at that moment.
But the problem is, I cannot press the "STOP" button as it does not allow me, since there's an ongoing process.
Is there a way to overcome this?
I know there's something called "MethodInvoker", but I have no idea how that works or whether it is relevant to this.
private bool keepRunning = true;
public Form1()
{
InitializeComponent();
}
private void StartBtn_Click(object sender, EventArgs e)
{
var number = 1;
while (keepRunning)
{
Thread.Sleep(1000);
MesgeLabel.Text = "" + number++;
}
}
private void StopBtn_Click(object sender, EventArgs e)
{
//Cannot even click this button
keepRunning = false;
//or
Application.Exit();
}
EDIT 1:
If you need to interact with UI controls, doing it from a background task would throw invalid operation -> illegal cross thread exception. To overcome this,
check Control.InvokeRequired
if(myLabel.InvokeRequired)
myLabel.Invoke(new Action(() => myLabel.Text = newText));
else
myLabel.Text = newText;
You can start a Task by providing a CancellationToken and cancel the operation when the stop button is clicked.
The task will execute the infinite loop on another thread and your main thread (the UI thread) should not be affected and should be accessible.
Try this:
/*
Please add these on top of your form class
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
*/
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
CancellationTokenSource cancellationTokenSource;
CancellationToken cancellationToken;
private void CountToInfinity()
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
Debug.WriteLine(new Random().Next());
}
}
private async void button1_Click(object sender, EventArgs e)
{
if (cancellationTokenSource == null)
{
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
Task.Run((Action)CountToInfinity, cancellationToken);
}
}
private void button2_Click(object sender, EventArgs e)
{
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
}
}
If you have spawned a new process then you can call kill method.
Process myProcess = Process.Start("Notepad.exe")//starts new process
myProcess.Kill();// kills the process. save reference to myProcess and call kill on STOP button click
If you have started new thread then call abort method to stop the thread.
Thread thread = new Thread(new ThreadStart(method));
thread.Start();
thread.Abort(); // terminates the thread. call abort on STOP button click
When you press the "start" button, the code that runs and prints the numbers will run on the ui thread. (from your explanation, i assume that all you have is the message handler for the button press event and nothing else. e.g.: Not setting up a seperate thread.).
Running an infinite loop on the ui thread means, that you do not get any more time for processing other messages. (the thread that is responsible for processing the ui messages is stuck in your infinite loop.)
So, in order to be able to press the "stop" button, you need to run the code with the infinite loop in a different thread or in a different process altogether. This is what Arjun is trying to tell you. (if you want the code in the infinite loop to access resources from your form app, you need a thread. [the thread is inside the forms app process.])
please note: if you create a thread and run your number printing code inside that thread, this will not be the ui thread. Thus, you will not be able to interact with the forms controls as if you'd be on the ui thread. (i.e.: trying to set the windows.text in order to display your numbers will most likely throw an exception.)
I created a Windows Forms application (C#, .NET Framework 4.5) that graphs the data received via the serial line (ex. a sensor attached to a microcontroller). For the graphing I am using ZedGraph library. The problem I ran into is that if I click on the titlebar when the data is being received and plotted, the graph freezes for the duration of the click. As soon as I release the left mouse button, it normally continues the plotting, except that it now just connects the data point received right before the click with the one received right after the mouse button was released. An example is shown below:
This seems to happen only with the graphing part, since the program also outputs the received data to a file. If I plot the received data in another program after the measurement (Excel, Matlab, ...), all the data is as it should be, without any interruptions.
I tried to find what the reason might be, but I could not find a similar example anywhere, so any help would be appreciated.
In order to simplify the process of finding the solution I have also created a smaller program that simply prints the time from the stopwatch to a textBox. Here is the code:
namespace ClickFreezeTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
System.Threading.Thread t = new System.Threading.Thread(() => updateTextBox());
t.IsBackground = true;
t.Start();
}
private void updateTextBox()
{
Stopwatch myStopWatch = new Stopwatch();
myStopWatch.Start();
int a = 1;
while (a == 1)
{
textBox1.BeginInvoke(new Action(() => { textBox1.Text = Convert.ToString(myStopWatch.Elapsed); }));
Thread.Sleep(100);
}
}
}
}
The form looks like this:
As shown in the code above, the textBox is updated every 100 ms on a separate thread. The same problem appears as with the serial graphing program, so I am assuming that if the solution exists it has to do something with the form settings, maybe a control that allows the user to do something with the mouse.
I've got a WinForms project that scans a given network and returns valid IP addresses. Once all the addresses are found, I create a user control for each and place it on the form. My functions to ping ip addresses use async and Task which I thought would "wait" to execute before doing something else, but it doesn't. My form shows up blank, then within 5 seconds, all the user controls appear on the form.
Declarations:
private List<string> networkComputers = new List<string>();
Here's the Form_Load event:
private async void MainForm_Load(object sender, EventArgs e)
{
//Load network computers.
await LoadNetworkComputers();
LoadWidgets();
}
The LoadNetworkComputers function is here:
private async Task LoadNetworkComputers()
{
try
{
if (SplashScreenManager.Default == null)
{
SplashScreenManager.ShowForm(this, typeof(LoadingForm), false, true, false);
SplashScreenManager.Default.SetWaitFormCaption("Finding computers");
}
else
Utilities.SetSplashFormText(SplashForm.SplashScreenCommand.SetLabel, "Scanning network for computers. This may take several minutes...");
networkComputers = await GetNetworkComputers();
}
catch (Exception e)
{
MessageBox.Show(e.Message + Environment.NewLine + e.InnerException);
}
finally
{
//Close "loading" window.
SplashScreenManager.CloseForm(false);
}
}
And the last 2 functions:
private async Task<List<string>> GetNetworkComputers()
{
networkComputers.Clear();
List<string> ipAddresses = new List<string>();
List<string> computersFound = new List<string>();
for (int i = StartIPRange; i <= EndIPRange; i++)
ipAddresses.Add(IPBase + i.ToString());
List<PingReply> replies = await PingAsync(ipAddresses);
foreach(var reply in replies)
{
if (reply.Status == IPStatus.Success)
computersFound.Add(reply.Address.ToString());
}
return computersFound;
}
private async Task<List<PingReply>> PingAsync(List<string> theListOfIPs)
{
var tasks = theListOfIPs.Select(ip => new Ping().SendPingAsync(ip, 2000));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
I'm really stuck on why the form is being displayed before the code in the MainForm_Load event finishes.
EDIT
I forgot to mention that in the LoadNetworkComputers it loads a splash form which lets the user know that the app is running. It's when the form shows up behind that, that I'm trying to avoid. Here's a screenshot (sensitive info has been blacked out):
The reason one would use async-await is to enable callers of functions to continue executing code whenever your function has to wait for something.
The nice thing is that this will keep your UI responsive, even if the awaitable function is not finished. For instance if you would have a button that would LoadNetworkComputers and LoadWidgets you would be glad that during this relatively long action your window would still be repainted.
Since you've defined your Mainform_Loadas async, you've expressed that you want your UI to continue without waiting for the result of LoadNetWorkComputers.
In this interview with Eric Lippert (search in the middle for async-await) async-await is compared with a a cook making dinner. Whenever the cook finds that he has to wait for the bread to toast, he starts looking around to see if he can do something else, and starts doing it. After a while when the bread is toasted he continues preparing the toasted bread.
By keeping the form-load async, your form is able to show itself, and even show an indication that the network computers are being loaded.
An even nicer method would be to create a simple startup-dialog that informs the operator that the program is busy loading network computers. The async form-load of this startup-dialog could do the action and close the form when finished.
public class MyStartupForm
{
public List<string> LoadedNetworkComputers {get; private set;}
private async OnFormLoad()
{
// start doing the things async.
// keep the UI responsive so it can inform the operator
var taskLoadComputers = LoadNetworkComputers();
var taskLoadWidgets = LoadWidgets();
// while loading the Computers and Widgets: inform the operator
// what the program is doing:
this.InformOperator();
// Now I have nothing to do, so let's await for both tasks to complete
await Task.WhenAll(new Task[] {taskLoadComputers, taskLoadWidgets});
// remember the result of loading the network computers:
this.LoadedNetworkComputers = taskLoadComputers.Result;
// Close myself; my creator will continue:
this.Close();
}
}
And your main form:
private void MainForm_Load(object sender, EventArgs e)
{
// show the startup form to load the network computers and the widgets
// while loading the operator is informed
// the form closes itself when done
using (var form = new MyStartupForm())
{
form.ShowDialog(this);
// fetch the loadedNetworkComputers from the form
var loadedNetworkComputers = form.LoadedNetworkComputers;
this.Process(loadedNetworkComputers);
}
}
Now while loading, instead of your mainform the StartupForm is shown while the items are loaded.. The operator is informed why the main form is not showing yet. As soon as loading is finished, the StartupForm closes itself and loading of the main form continues
My form shows up blank, then within 5 seconds, all the user controls appear on the form.
This is by design. When the UI framework asks your app to display a form, it must do so immediately.
To resolve this, you'll need to decide what you want your app to look like while the async work is going on, initialize to that state on startup, and then update the UI when the async work completes. Spinners and loading pages are a common choice.
my application calls crystal reports viewer to display a report. I run the report viewer in a separate thread. It works just fine. it displays the report properly. my problem is i want to kill the report while it is processing if it is taking too long to run. While the report is processing the busy indicator is spinning and it seems to block any UI on the report viewer form. My report viewer form has a crystal reports viewer on it along with a close button at the bottom of the form itself. i would like to be able to click the close button and have it stop the processing. Here is my code to run the viewer in a single apartment thread
public void RunReportStep1(UAReport report)
{
UAReportService service = new UAReportService(report);
service.RunReport();
var reportDocument = service.ReportDocument;
Thread staThread = new Thread(r => { RunReportStep2((ReportDocument) r); });
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(reportDocument);
staThread.Join();
}
public void RunReportStep2(ReportDocument reportDocument)
{
ReportViewerForm form = new ReportViewerForm(reportDocument);
form.BringToFront();
form.ShowDialog();
if (form.DialogResult == DialogResult.OK)
{
}
what is the best way to kill the thread from within the report viewer form while the processing is going on. Once the processing completes and the report is destroyed closing the report is no problem. It's only a problem while the processing is going on before the report is displayed. While the report is processing the close button is not responsive. sometimes if i click it repeatedly i can get a response and the report cancels. but it is not consistent and i have to click it repeatedly. that is not acceptable for my clients to have to do.
Call the service.RunReport() from another thread and wait for that thread to finish or for a certain amount of time to pass. I didn't write all the code for this, but anything I didn't write I a least described.
// Global so it can be reached from both threads
UAReportService service;
// Global variable that is written to when the report doc is ready:
ReportDocType reportDoc; //Can't use var here unfortunately
public void RunReportStep1(UAReport report)
{
service = new UAReportService(report);
Thread staThread = new Thread(r => { RunReportStep2((UAReportService)r); });
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(service);
// Save time the thread started:
DateTime start = DateTime.Now;
// Display a loading box (label or something, up to you).
// Then this function will end and your user can do stuff in the UI again (like press Cancel).
myLoadingBox.Visible = true;
}
public void RunReportStep2(UAReportService service)
{
service.RunReport();
reportDocument = service.ReportDocument;
}
// Call this new function periodically to see if the service thread finished, maybe once every second.
public checkAndDisplay()
{
// If thread is finished or 30 seconds have passed.
if (staThread.IsAlive == false || (DateTime.Now - start).TotalSeconds > 30)
{
// Show a message saying report failed to run
}
else // Else you can now show your report viewer
{
ReportViewerForm form = new ReportViewerForm(reportDocument);
form.BringToFront();
form.ShowDialog();
if (form.DialogResult == DialogResult.OK)
{
}
}
}
// Code for cancelling the report service call if user presses cancel button.
private void CancelButton_Click(object sender, EventArgs e)
{
// Terminate the report service thread.
staThread.Abort();
// Abort() isn't the nicest way to do this so if you can set a timeout on the UAReportService object (where it quits running after so long) that would be better.
// Or have it periodically check a variable to see if it should quit (see link below)
}
MSDN example of gracefully closing a thread.
In my MainWindow I have a button that can be used to open a Process (native OpenProcess call) and perform some checks on it's memory, but the method called on Click is asynchronous:
<Button Content="Attach" Click="OnClickAttach"/>
private async void OnClickAttach(Object sender, RoutedEventArgs e)
{
AttachmentResult result = await m_ViewModel.Attach();
switch (result)
// Different MessageBox depending on the result.
}
Now, let's see the ViewModel portion of code...
// MemoryProcess class is just a wrapper for Process' handle and memory regions.
private MemoryProcess m_MemoryProcess;
public async Task<AttachmentResult> Attach()
{
AttachmentResult result = AttachmentResult.Success;
MemoryProcess memoryProcess = NativeMethods.OpenProcess(m_SelectedBrowserInstance.Process);
if (memoryProcess == null)
result = AttachmentResult.FailProcessNotOpened;
else
{
Boolean check1 = false;
Boolean check2 = false;
foreach (MemoryRegion region in memoryProcess)
{
// I perform checks on Process' memory regions and I eventually change the value of check1 or check2...
await Task.Delay(1);
}
if (!check1 && !check2)
{
NativeMethods.CloseHandle(memoryProcess.Handle);
result = AttachmentResult.FailProcessNotValid;
}
else
{
// I keep the Process opened for further use. I save it to a private variable.
m_MemoryProcess = memoryProcess;
m_MemoryProcess.Check1 = check1;
m_MemoryProcess.Check2 = check2;
}
}
return result;
}
Now... here comes the problem. When the user closes the application, if a Process is opened, I must properly close its handle. So in my MainWindow I have the following code:
protected override void OnClosing(CancelEventArgs e)
{
m_ViewModel.Detach();
base.OnClosing(e);
}
And in my ViewModel I have the following code:
public void Detach()
{
if (m_MemoryProcess != null)
{
if (m_MemoryProcess.Check1)
// Do something...
if (m_MemoryProcess.Check2)
// Do something...
NativeMethods.CloseHandle(m_MemoryProcess.Handle);
m_MemoryProcess = null;
}
}
The Attach() method can take very long time, more than 2 minutes sometimes. I need to find a solution for the following issues:
If the user closes the application while Attach() method is running and before memoryProcess is saved to the private variable, the Process handle will not be closed.
If I save the MemoryProcess instance to the private variable just at the beginning of the Attach() method, there is a risk for the user to get a NullReferenceException if he closes the application while the Attach() method is processing its foreach loop.
I absolutely don't want to make the user wait for Attach() method to complete before letting him close the application. That's horrible.
How can I do this?
IMO, if you do not explicitly and specifically target to create separate detached/independent processes like, for example, through:
using PInvoke.CreateProcess
using
(new System.Management.ManagementClass("Win32_ProcessStartup"))
.Properties["CreateFlags"].Value = 8;
or maintaining child process alive upon app closing by launching them through separate shell scripts or other processes remaining to run after app closing;
creating a new thread in another independent process using CreateRemoteThread
etc.
or finding already run independently processes, you don't need to and probably should not "close" or dispose spawned by app processes. Windows (operting system) will close any unclosed spawned by app processes.
Also, I believe that it is impossible to execute any code in an application once it has started exiting or being closed.
PS (off-topic comment):
I do not even see that you close (really one should kill) or dispose your processes in your code...