Archive
HowTo: use a Timer component for delayed execution in WinForms
This is my contribution to:
http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms
Sometimes a Timer component is useful and easy to setup in WinForms, just set its interval and then enable it, then make sure the first thing you do in its Tick event handler is to disable itself.
I think Timer runs the code in its own thread, so you may still need to do a BeginInvoke (called upon the WinForm object [this]) to run your Action.
private WebBrowserDocumentCompletedEventHandler handler;
//need to make it a class field for the handler below
//(anonymous delegates seem to capture state at point of
// definition, so they can't capture their own reference) private string imageFilename; private bool exit; public void CaptureScreenshot(
Uri address = null,
string imageFilename = null,
int msecDelay = 0,
bool exit = false) { handler = (s, e) => { webBrowser.DocumentCompleted -= handler; //must do first this.imageFilename = imageFilename; this.exit = exit; timerScreenshot.Interval = (msecDelay>0)? msecDelay : 1; timerScreenshot.Enabled = true; }; webBrowser.DocumentCompleted += handler; Go(address); //if address == null, will use URL from UI } private void timerScreenshot_Tick(object sender, EventArgs e) { timerScreenshot.Enabled = false; //must do first BeginInvoke((Action)(() => //Invoke at UI thread { //run in UI thread BringToFront(); Bitmap bitmap = webBrowser.GetScreenshot(); if (imageFilename == null) imageFilename = bitmap.ShowSaveFileDialog(); if (imageFilename != null) { Directory.CreateDirectory(
Path.GetDirectoryName(
Path.GetFullPath(imageFilename)));
//create any parent directories needed bitmap.Save(imageFilename); } bitmap.Dispose(); //release bitmap resources if (exit) Close(); //this should close the app, this is main form }), null); }
You can see the above in action at ClipFlair‘s WebCapture tool (http://gallery.clipflair.net/WebCapture, source code at: http://ClipFlair.codeplex.com, see Tools/WebCapture folder) that grabs screenshots from websites.
BTW, if you want to call the executable from command-line make sure you go to Properties of the project and at Security tab turn-off ClickOnce security (else it can’t access command-line).
Gotchas at Wait for a shelled app to finish (with/out timeout) with .NET
I came across a useful Microsoft Support sample called “How to wait for a shelled application to finish by using Visual Basic 2005 or Visual Basic .NET” at http://support.microsoft.com/kb/305368
However, note that there are several gotchas with the code supplied there (just informed Microsoft on that, hope they take notice). Also the article points to C# and to C++ versions of the sample that obviously need the same fixes
1) there’s an issue in both the first sample (wait indefinitely) and the 2nd one (wait with timeout)
‘Wait for the process window to complete loading.
p.WaitForInputIdle()
‘Wait for the process to exit.
p.WaitForExit()
Why wait for input idle first? The process might never enter idle state and exit before that. According to http://msdn.microsoft.com/en-us/library/8d7363e2(v=VS.90).aspx you might get exception from WaitForInputIdle:
Exception | Condition |
InvalidOperationException | The process does not have a graphical interface. -or- An unknown error occurred. The process failed to enter an idle state. -or- The process has already exited. -or- No process is associated with this Process object. |
I suppose it’s best to avoid calling WaitForInputIdle at all since you just care for WaitForExit there.
2) even WaitForExit can throw exceptions that the code should check for according to http://msdn.microsoft.com/en-us/library/fb4aw7b8.aspx
Exception | Condition |
Win32Exception | The wait setting could not be accessed. |
SystemException | No process Id has been set, and a Handle from which the Id property can be determined does not exist. -or- There is no process associated with this Process object. -or- You are attempting to call WaitForExit for a process that is running on a remote computer. This method is available only for processes that are running on the local computer. |
3) The support article doesn’t mention what WaitForExit(timeout) doc (http://msdn.microsoft.com/en-us/library/fb4aw7b8.aspx) says about “infinite” timeout:
Note
In the .NET Framework version 3.5 and earlier versions, the WaitForExit overload waited for MaxValue milliseconds (approximately 24 days), not indefinitely. Also, previous versions did not wait for the event handlers to exit if the full MaxValue time was reached.
Also it seems the documentation for “WaitForExit(timeout)” doesn’t mention there’s a Timeout.Infinite constant that has the value –1 to use for such infinite timeouts (found it from the doc of Thread.Join): http://msdn.microsoft.com/en-us/library/system.threading.timeout.infinite(v=VS.90).aspx
4) The sample fails to call Close and thus keeps on spending resources for handle tracking (and “locking” those handle ids obviously although it’s not as easy as in old CPUs and OS versions to run out of handles I hope).
Quoting http://msdn.microsoft.com/en-us/library/fb4aw7b8.aspx:
When an associated process exits (that is, when it is shut down by the operation system through a normal or abnormal termination), the system stores administrative information about the process and returns to the component that had called WaitForExit. The Process component can then access the information, which includes the ExitTime, by using the Handle to the exited process.
Because the associated process has exited, the Handle property of the component no longer points to an existing process resource. Instead, the handle can be used only to access the operating system’s information about the process resource. The system is aware of handles to exited processes that have not been released by Process components, so it keeps the ExitTime and Handle information in memory until the Process component specifically frees the resources. For this reason, any time you call Start for a Process instance, call Close when the associated process has terminated and you no longer need any administrative information about it. Close frees the memory allocated to the exited process.
Also note that it could have a “Using” clause when defining the new process object (instead of Dim) instead of explicitly having to call “Process.Close” at the end to free up resources, as noted at http://msdn.microsoft.com/en-us/library/system.diagnostics.process.close(v=VS.90).aspx
The Close method causes the process to stop waiting for exit if it was waiting, closes the process handle, and clears process-specific properties. Close does not close the standard output, input, and error readers and writers in case they are being referenced externally.
Note:
The Dispose(Boolean) method calls Close. Placing the Process object in a using block disposes of resources without the need to call Close.
5) Other issue is at the 2nd sample (wait with timeout). It doesn’t mention having to call WaitForExit() again with no params after the WaitForExit(timeout) at the case where standard output has been redirected to async event handlers (should mention this case for completeness)
Quoting http://msdn.microsoft.com/en-us/library/fb4aw7b8.aspx:
When standard output has been redirected to asynchronous event handlers, it is possible that output processing will not have completed when this method returns. To ensure that asynchronous event handling has been completed, call the WaitForExit overload that takes no parameter after receiving a true from this overload. To help ensure that the Exited event is handled correctly in Windows Forms applications, set the SynchronizingObject property.