I'm experiencing a rather strange hang with an indefinitely climbing memory leak when calling OpenFileDialog.ShowDialog (.NET Framework 4.8). As shown in the animated gif/video (apologies for the lack of editing), there seems to be no noteworthy change in the snapshots regardless of how long I wait - even when it reaches multiple allocated gigabytes. Am I missing something regarding how to use the Memory Usage snapshot diagnostic tool?
The initial folder it tries to access doesn't have very many files and opening that same folder using FileDialog boxes in other applications (such as Visual Studio 2019) doesn't result in any problems.
There is no funky timer code anywhere in the application, it does run tasks (as shown below) to load the data in the background for a more responsive UI, however the dialog is intentionally loaded in a section of code where the logic path is guaranteed to be running on the UI thread to simplify matters. The same code worked fine for years, before, the only code I've added since this odd behaviour began is to add report printing functionality in an unrelated section of code.
Any ideas? Thanks.
Progress update:
I have only one Shell extension (WinMerge), it's been working fine for years.
I've tested the program in a VM and it works exactly as intended.
I've downloaded the debug PDB symbols using the Microsoft and NuGet.org symbol servers, and the leak is definitely happening in unmanaged Windows code - the memory usage tool is referring to the leaked objects as an endlessly growing list of opaque (void*) memory addresses and values within an "Object Type" named "unresolved allocations", unfortunately. A sample callstack is as follows:
ntdll.dll!NtTraceEvent()
ntdll.dll!RtlpLogHeapFreeEvent()
ntdll.dll!RtlpFreeHeap()
ntdll.dll!RtlpFreeHeapInternal()
ntdll.dll!RtlFreeHeap()
ntdll.dll!RtlDebugFreeHeap()
ntdll.dll!RtlpFreeHeap()
ntdll.dll!RtlpFreeHeapInternal()
ntdll.dll!RtlFreeHeap()
KernelBase.dll!LocalFree()
ExplorerFrame.dll!CTContainer_PolicyLocalMem::DestroyMem(void *)
ExplorerFrame.dll!TabletModeHelpers::GetMonitorConfig(int *, bool *)
ExplorerFrame.dll!SHIsFileExplorerInTabletMode(void)
ExplorerFrame.dll!UICheckbox::OnHosted()
dui70.dll!DirectUI::Element::OnHosted()
ExplorerFrame.dll!UIItem::OnHosted(class DirectUI::Element *)
dui70.dll!DirectUI::Element::OnPropertyChanged()
ExplorerFrame.dll!UIItem::OnPropertyChanged()
dui70.dll!DirectUI::Element::_PostSourceChange()
dui70.dll!DirectUI::Element::Insert()
ExplorerFrame.dll!UICollection::_CreateAndInsertItems()
ExplorerFrame.dll!UICollection::_RealizeItems()
ExplorerFrame.dll!UICollection::RealizeSection(class SectionInfo *)
ExplorerFrame.dll!LineScroller::_RealizeDirectionWorker()
ExplorerFrame.dll!LineScroller::_RealizeDirectionWorker()
ExplorerFrame.dll!LineScroller::_RealizeDirectionWorker()
ExplorerFrame.dll!LineScroller::_RealizeAroundAnchor()
ExplorerFrame.dll!LineScroller::_RealizeContent()
ExplorerFrame.dll!LineScroller::_LayoutContent()
ExplorerFrame.dll!LineScroller::_Viewer_SelfLayoutDoLayout(int,int)
dui70.dll!DirectUI::Element::_FlushLayout()
dui70.dll!DirectUI::Element::_FlushLayout()
dui70.dll!DirectUI::Element::_FlushLayout()
dui70.dll!DirectUI::Element::_FlushLayout()
dui70.dll!DirectUI::Element::_FlushLayout()
dui70.dll!DirectUI::DeferCycle::_EndDefer()
dui70.dll!DirectUI::Element::EndDefer()
dui70.dll!DirectUI::Element::_PostSourceChange()
dui70.dll!DirectUI::Element::_SetValue()
dui70.dll!DirectUI::Element::SetValue(struct DirectUI::PropertyInfo const * (*)(void),int,class DirectUI::Value *)
ExplorerFrame.dll!LineViewer::SetLayoutState(int)
ExplorerFrame.dll!LineScroller::_EnsureRealizePass(void)
ExplorerFrame.dll!LineScroller::_OnLayoutChangeEvent(struct LayoutChangeEvent *)
ExplorerFrame.dll!LineScroller::OnEvent()
dui70.dll!DirectUI::Element::s_HandleDUIEventMessage(class DirectUI::Element *,struct EventMsg *)
dui70.dll!DirectUI::Element::_DisplayNodeCallback()
duser.dll!GPCB::xwInvokeDirect()
duser.dll!SafeMsgQ::xwProcessNL()
duser.dll!SafeMsgQ::xwProcessNL(void)
duser.dll!CoreSC::xwProcessMsgQNL(void)
duser.dll!CoreSC::xwProcessNL()
duser.dll!MphProcessMessage()
user32.dll!__ClientGetMessageMPH()
ntdll.dll!KiUserCallbackDispatcherContinue()
win32u.dll!NtUserPeekMessage()
user32.dll!_PeekMessage()
user32.dll!DialogBox2()
user32.dll!InternalDialogBox()
user32.dll!DialogBoxIndirectParamAorW()
user32.dll!DialogBoxIndirectParamW()
comdlg32.dll!<lambda>(void)()
comdlg32.dll!CFileOpenSave::Show(struct HWND__*)
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.FileDialog.RunDialogVista(System.IntPtr hWndOwner)
System.Windows.Forms.dll!System.Windows.Forms.CommonDialog.ShowDialog(System.Windows.Forms.IWin32Window owner)
Bingo.exe!OPEQ.frmBingo.tsmiFileOpen_Click(object sender, System.EventArgs e) Line 1098
private void tsmiFileOpen_Click(object sender, EventArgs e)
{
Logging.Method();
//Gets the filter description retrieval task for the dialog
//box which was begun at program launch (if this fails or has
//yet to complete, the initial value is used instead).
CultureInfo uiCulture = Thread.CurrentThread.CurrentUICulture;
TaskParams openDialogFilterTaskParams;
if (_tasks.TryGetValue(_fileDialogFilterTaskKey, out openDialogFilterTaskParams))
{
bool removeFromTasks = false;
//If we can, get the result, otherwise use the existing filter.
switch (openDialogFilterTaskParams.Task.Status)
{
case TaskStatus.RanToCompletion:
//Update the filter variable.
var odfTask = (Task<IDictionary<CultureInfo, FileDialogFilterList>>)openDialogFilterTaskParams.Task;
foreach (var kvPair in odfTask.Result)
{
//If any GetFileTypeName call fails, the resulting
//description is null, so we need to
//account for that here.
var updatedList = ReplaceNullDescriptions(kvPair.Value, kvPair.Key);
_openDialogFilterDict[kvPair.Key] = updatedList.CopyEmbellished().ToString();
}
removeFromTasks = true;
break;
case TaskStatus.Canceled:
case TaskStatus.Faulted:
removeFromTasks = true;
//Use the filter's initial value.
break;
default:
//Use the filter's initial value.
break;
}
if (removeFromTasks)
{
try
{
//The Wait() here is intentional to avoid
//making this event handler async.
openDialogFilterTaskParams.Task.Wait(openDialogFilterTaskParams.TokenSource.Token);
}
catch(OperationCanceledException)
{
//Swallow cancellation.
}
catch (AggregateException aggEx) when (aggEx.InnerExceptions.Any(ex => !(ex is OperationCanceledException)))
{
//Log and swallow the exception(s).
Logging.Exception(aggEx,
isRecoverableError: true,
includeStackTrace: true);
}
finally
{
openDialogFilterTaskParams.Task.Dispose();
openDialogFilterTaskParams.TokenSource.Dispose();
_tasks.TryRemove(_fileDialogFilterTaskKey, out _);
}
}
}
//Dialog box guarantees the user-specified file exists.
//No need to handle initial directory, it handles that on its own.
openFileDialog1.Filter = _openDialogFilterDict[uiCulture];
openFileDialog1.DefaultExt = _openDialogDefaultExtension;
//Program hangs on this next line.
if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
{
//Handle the creation, dictionary addition and retrieval as one atomic operation.
var fileLoadTaskParams = _tasks.GetOrAdd(_fileLoadingTaskKey, (_, fnames) =>
{
TaskParams tParam = new TaskParams(
null,
null,
new Progress<ReportLoaderProgressChangedEventArgs>());
try
{
tParam.TokenSource = CancellationTokenSource.CreateLinkedTokenSource(_tidyShutdownCts.Token);
tParam.Task = new ReportLoader(fnames).LoadAsync(tParam.TokenSource.Token, (IProgress<ReportLoaderProgressChangedEventArgs>)tParam.Additional);
}
catch
{
tParam.TokenSource?.Dispose();
tParam.Task?.Dispose();
throw;
}
return tParam;
}, openFileDialog1.FileNames.AsEnumerable());
//Set the default scan-type mode
//(Search if multiple files were loaded,
//Verification if only one).
_scanModeSelected = openFileDialog1.FileNames.Length > 1
? tsBtnScanType_Search
: tsBtnScanType_Verification;
/* Hand off the contents of the retrieved tuple to
* LoadFile(...). This means LoadFile handles the display
* of any non-critical exceptions to the user, anything
* dangerous will bubble out of it and into FireAndForget at
* which point the application is allowed to die.*/
LoadFile(
(Task<OPEQReportDataSet>)fileLoadTaskParams.Task,
fileLoadTaskParams.TokenSource,
(Progress<ReportLoaderProgressChangedEventArgs>)fileLoadTaskParams.Additional)
.FireAndForget(null, null, () =>
{
fileLoadTaskParams.Task.Dispose();
fileLoadTaskParams.TokenSource?.Dispose();
_tasks.TryRemove(_fileLoadingTaskKey, out _);
});
} //if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
}
2nd Progress Update:
...and it's now working again without explanation. I guess it was indeed a Windows Update. (?!)
Related
I have thef ollowing background worker in my app which is meant to start a user's session automatically if there is not already one available.
This is done on a backgroundworker (backgroundInit) on initialisation. As you can see below, I have a while loop which continues to run as long as the var checker remains false:
var checker = false;
var i = 0;
while (checker == false)
{
_session = funcs.GetSession(_servers, _name);
_sessID = _session[0].Trim();
_servName = _session[1];
checker = funcs.CheckRunning("lync.exe");
i++;
if (i > 200)
{
break;
}
}
The CheckRunning method just checks if a specified program (in this case, "lync") is currently running and returns either true or false accordingly (This is done via a CMD command).
When I run the app in an empty session however, the while loop only iterates one time before breaking out, even though "Lync" is definitely not running.
Is there any reason why running a process or too many processes from within a Backgroundworker may cause it to exit?
As the comments mentioned, this was not an issue with the BackgroundWorker, but rather an exception occurring at _sessID = session[0].Trim(); where the session had not yet started, so there is no ID.
To resolve this, I simply placed a Try/Catch block around this assignment, and let the program silently ignore the exception:
try
{
_sessID = _session[0].Trim();
_servName = _session[1];
}
catch (Exception exp)
{
// MessageBox.Show(exp.Message);
}
This works for me, as the loop will continue checking until the counter i reaches the 200 limit, at which stage the program will accept failure.
I currently just inherited some complex code that unfortunately I do not fully understand. It handles a large number of inventory records inputting/outputting to a database. The solution is extremely large/advanced where I am still on the newer side of c#. The issue I am encountering is that periodically the program will throw an IO Exception. It doesn't actually throw a failure code, but it messes up our output data.
the try/catch block is as follows:
private static void ReadRecords(OleDbRecordReader recordReader, long maxRows, int executionTimeout, BlockingCollection<List<ProcessRecord>> processingBuffer, CancellationTokenSource cts, Job theStack, string threadName) {
ProcessRecord rec = null;
try {
Thread.CurrentThread.Name = threadName;
if(null == cts)
throw new InvalidOperationException("Passed CancellationToken was null.");
if(cts.IsCancellationRequested)
throw new InvalidOperationException("Passed CancellationToken is already been cancelled.");
long reportingFrequency = (maxRows <250000)?10000:100000;
theStack.FireStatusEvent("Opening "+ threadName);
recordReader.Open(maxRows, executionTimeout);
theStack.FireStatusEvent(threadName + " Opened");
theStack.FireInitializationComplete();
List<ProcessRecord> inRecs = new List<PIRecord>(500);
ProcessRecord priorRec = rec = recordReader.Read();
while(null != priorRec) { //-- note that this is priorRec, not Rec. We process one row in arrears.
if(cts.IsCancellationRequested)
theStack.FireStatusEvent(threadName + " cancelling due to request or error.");
cts.Token.ThrowIfCancellationRequested();
if(rec != null) //-- We only want to count the loop when there actually is a record.
theStack.RecordCountRead++;
if(theStack.RecordCountRead % reportingFrequency == 0)
theStack.FireProgressEvent();
if((rec != null) && (priorRec.SKU == rec.SKU) && (priorRec.Store == rec.Store) && (priorRec.BatchId == rec.BatchId))
inRecs.Add(rec); //-- just store it and keep going
else { //-- otherwise, we need to process it
processingBuffer.Add(inRecs.ToList(),cts.Token); //-- note that we don't enqueue the original LIST! That could be very bad.
inRecs.Clear();
if(rec != null) //-- Again, we need this check here to ensure that we don't try to enqueue the EOF null record.
inRecs.Add(rec); //-- Now, enqueue the record that fired this condition and start the loop again
}
priorRec = rec;
rec = recordReader.Read();
} //-- end While
}
catch(OperationCanceledException) {
theStack.FireStatusEvent(threadName +" Canceled.");
}
catch(Exception ex) {
theStack.FireExceptionEvent(ex);
theStack.FireStatusEvent("Error in RecordReader. Requesting cancellation of other threads.");
cts.Cancel(); // If an exception occurs, notify all other pipeline stages, then rethrow
// throw; //-- This will also propagate Cancellation, but that's OK
}
In the log of our job we see the output loader stopping and the exception is
System.Core: Pipe is broken.
Does any one have any ideas as to what may cause this? More importantly, the individual who made this large-scale application is no longer here. When I debug all of my applications, I am able to add break points in the solution and do the standard VS stepping through everything to find the issue. However, this application is huge and has a GUI that pops up when I debug the application. I believe the GUI was made for testing purposes, but it hinders me from actually being able to step through everything. However when the .exe is run from our actual job stream, there is no GUI it just executes the way it's supposed to.
The help I am asking for is 2 things:
just suggestions as to what may cause this. Could an OleDB driver be the cause? Reason I ask is because I have this running on 2 different servers. One test and one not. The one with a new OleDB driver version does not fail (7.0 i believe whereas the other where it fails is 6.0).
Is there any code that I could add that may give me a better indication as to what may be causing the broken pipe? The error only happens periodically. If I run the job again right after, it may not happen. I'd say it's 30-40% of the time it throws the exception.
If you have any additional questions about the structure just let me know.
I've a got a problem with the infamous message "The thread xxx has exited with code 0 (0x0)".
In my code I have a main class called "Load" that starts with a Windows Form load event:
public class Load
{
public Load()
{
Device[] devices = GetDevices(); // Get an array of devices from an external source
for (int i = 0; i < devices.Length; i++)
{
DeviceDiagnosticCtrl deviceDiagnostic = new DeviceDiagnosticCtrl(devices[i].name);
}
}
}
Inside the constructor, for each generic device read from an external source, I initialize a custom diagnostic class that runs a thread:
public class DeviceDiagnosticCtrl
{
private Thread diagnosticController;
private volatile bool diagnosticControllerIsRunning = false;
public DeviceDiagnosticCtrl(string _name)
{
// Thread initialization
this.diagnosticController = new Thread(new ThreadStart(this.CheckDiagnostic));
this.diagnosticController.Start();
this.diagnosticControllerIsRunning = true;
}
private void CheckDiagnostic()
{
while (this.diagnosticControllerIsRunning)
{
try
{
// Custom 'Poll' message class used to request diagnostic to specific device
Poll poll = new Poll();
// Generic Message result to diagnostic request
IGenericMessage genericResult;
// Use a custom driver to send diagnostic request
SendSyncMsgResult res = this.customDriver.SendSyncMessage(poll, out genericResult);
switch (res)
{
case SendSyncMessageResult.GOOD:
{
// Log result
}
break;
case SendSyncMessageResult.EXCEPTION:
{
// Log result
}
break;
}
Thread.Sleep(this.customDriver.PollScantime);
}
catch (Exception ex)
{
// Loggo exception
}
}
}
}
When I run the above code in debug mode I always read 8 devices from external source, and for each of them I continuously run a managed thread to retrieve diagnostic.
My problem is that randomly one or more of the 8 threads I expect from the code above exit with code 0, without any exception.
I've started/restarted the code in debug mode a lot of time, and almost everytime one of the thread exits.
I've read somewhere (i.e. this SO question) that it could depends of Garbage Collector action, but I'm not too sure if this is my case - and how to prevent it.
Do someone see something strange/wrong in the sample code I posted above? Any suggestion?
'while (this.diagnosticControllerIsRunning)' is quite likely to fail immediate, in which case the thread drops out. It's no good starting the thread and THEN setting 'this.diagnosticControllerIsRunning = true;' - you're quite likely to be too late.
Bolt/stable-door. Something like:
do{
lengthyStuff with Sleep() in it
}
while (this.diagnosticControllerRun);
Copied from Here
Right click in the Output window when you're running your program and
uncheck all of the messages you don't want to see (like Thread Exit
messages).
My following code fails with "...has already been registered as a source on the local computer" even though I'm doing checks first:
lock ( eventLock )
{
string eventLog = Constants.EventLogPL;
string eventSrc = Constants.EventSrcPL;
if (!EventLog.Exists(eventLog))
{
if (!EventLog.SourceExists(eventSrc))
{
try
{
EventLog.CreateEventSource(eventSrc, eventLog);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
}
}
I'd have thought my call to !EventLog.SourceExists would have been enough to prevent my error!
I'm on 2010 .NET 4 and Windows 7 64 compiling to any CPU.
Edit: Updated code to get Constant's to locals to check they don't change, and use locking to make sure only one thread can test and create. Code still fails with the same error.
Found the problem after digging in to Sysinternals' Process Monitor a little more:
Calling EventLog.Exists("MyLog");
Logs Name not found, as expected in:
KLM\System\CurrentControlSet\services\eventlog\MyLog
Calling EventLog.SourceExists("MySource");
Checks several places, name not found in as expected:
HKLM\System\CurrentControlSet\services\eventlog\Application\MySource
HKLM\System\CurrentControlSet\services\eventlog\HardwareEvents\MySource
HKLM\System\CurrentControlSet\services\eventlog\Internet Explorer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Key Management Service\MySource
HKLM\System\CurrentControlSet\services\eventlog\Media Center\MySource
HKLM\System\CurrentControlSet\services\eventlog\ODiag\MySource
HKLM\System\CurrentControlSet\services\eventlog\OSession\MySource
HKLM\System\CurrentControlSet\services\eventlog\Security\MySource
HKLM\System\CurrentControlSet\services\eventlog\System\MySource
HKLM\System\CurrentControlSet\services\eventlog\VisualSVNServer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Windows PowerShell\MySource
HKLM\System\CurrentControlSet\services\eventlog\Application\MySource
HKLM\System\CurrentControlSet\services\eventlog\HardwareEvents\MySource
HKLM\System\CurrentControlSet\services\eventlog\Internet Explorer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Key Management Service\MySource
HKLM\System\CurrentControlSet\services\eventlog\Media Center\MySource
HKLM\System\CurrentControlSet\services\eventlog\ODiag\MySource
HKLM\System\CurrentControlSet\services\eventlog\OSession\MySource
HKLM\System\CurrentControlSet\services\eventlog\Security\MySource
HKLM\System\CurrentControlSet\services\eventlog\System\MySource
HKLM\System\CurrentControlSet\services\eventlog\VisualSVNServer\MySource
HKLM\System\CurrentControlSet\services\eventlog\Windows PowerShell\MySource
HKLM\System\CurrentControlSet\services\eventlog\MyLog
However, calling EventLog.CreateEventSource("MySource", "MyLog");
Finds MyLog in the following registry location and errors:
HKLM\System\CurrentControlSet\services\eventlog\Application\MyLog
Removing "HKLM\System\CurrentControlSet\services\eventlog\Application\MyLog" and re-running fixed my problem!
Looks like the .Exists don't look in all the places .CreateEvent does!
//0 for false, 1 for true.
private static int usingResource = 0;
if (!EventLog.SourceExists(Constants.EventSrcPL))
{
//0 indicates that the method is not in use.
if (0 == Interlocked.Exchange(ref usingResource, 1))
{
if (!EventLog.SourceExists(Constants.EventSrcPL))
{
try
{
EventLog.CreateEventSource(Constants.EventSrcPL, Constants.EventLogPL);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
//Release the lock
Interlocked.Exchange(ref usingResource, 0);
}
}
}
}
else
{
usingResource = 0;
}
Does not solve the issue when the source is created by a different application in the exact time you are accessing the event log.
Edited: made modifications that account for the delayed creation of a EventSource.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
What would a piece of code which "uses exceptions to control flow" look like? I've tried to find a direct C# example, but cannot. Why is it bad?
Thanks
By definition, an exception is an occurrence which happens outside the normal flow of your software. A quick example off the top of my head is using a FileNotFoundException to see if a file exists or not.
try
{
File.Open(#"c:\some nonexistent file.not here");
}
catch(FileNotFoundException)
{
// do whatever logic is needed to create the file.
...
}
// proceed with the rest of your program.
In this case, you haven't used the File.Exists() method which achieves the same result but without the overhead of the exception.
Aside from the bad usage, there is overhead associated with an exception, populating the properties, creating the stack trace, etc.
It's roughly equivalent to a goto, except worse in terms of the word Exception, and with more overhead. You're telling the code to jump to the catch block:
bool worked;
try
{
foreach (Item someItem in SomeItems)
{
if (someItem.SomeTestFailed()) throw new TestFailedException();
}
worked = true;
}
catch(TestFailedException testFailedEx)
{
worked = false;
}
if (worked) // ... logic continues
As you can see, it's running some (made-up) tests; if they fail, an exception is thrown, and worked will be set to false.
Much easier to just update the bool worked directly, of course!
Hope that helps!
Bad
The below code catches an exception that could easily be avoided altogether. This makes the code more difficult to follow and typically incurs a performance cost as well.
int input1 = GetInput1();
int input2 = GetInput2();
try
{
int result = input1 / input2;
Output("{0} / {1} = {2}", input1, input2, result);
}
catch (OverflowException)
{
Output("There was an overflow exception. Make sure input2 is not zero.");
}
Better
This code checks for a condition that would throw an exception, and corrects the situation before the error occurs. This way there is no exception at all. The code is more readable, and the performance is very likely to be better.
int input1 = GetInput1();
int input2 = GetInput2();
while (input2 == 0)
{
Output("input2 must not be zero. Enter a new value.");
input2 = GetInput2();
}
int result = input1 / input2;
Output("{0} / {1} = {2}", input1, input2, result);
Here's a common one:
public bool TryParseEnum<T>(string value, out T result)
{
result = default(T);
try
{
result = (T)Enum.Parse(typeof(T), value, true);
return true;
}
catch
{
return false;
}
}
Probably the grossest violation I've ever seen:
// I haz an array...
public int ArrayCount(object[] array)
{
int count = 0;
try
{
while (true)
{
var temp = array[count];
count++;
}
}
catch (IndexOutOfRangeException)
{
return count;
}
}
I'm currently working with a 3rd party program that does this. They have a "cursor" interface (basically an IEnumerable alternative), where the only way to tell the program you're finished is to raise an exception. The code basically looks like:
// Just showing the relevant section
bool finished = false;
public bool IsFinished()
{
return finished;
}
// Using something like:
// int index = 0;
// int count = 42;
public void NextRecord()
{
if (finished)
return;
if (index >= count)
throw new APIProgramSpecificException("End of cursor", WEIRD_CONSTANT);
else
++index;
}
// Other methods to retrieve the current value
Needless to say, I hate the API - but its a good example of exceptions for flow control (and an insane way of working).
I'm not fond of C# but you can see some similarities between try-catch-finally statements and normal control flow statements if-then-else.
Think about that whenever you throw an exception you force your control to be passed to the catch clause. So if you have
if (doSomething() == BAD)
{
//recover or whatever
}
You can easily think of it in terms of try-catch:
try
{
doSomething();
}
catch (Exception e)
{
//recover or do whatever
}
The powerful thing about exception is that you don't have to be in the same body to alter the flow of the program, you can throw an exception whenever you want with the guarantee that control flow will suddently diverge and reach the catch clause. This is powerful but dangerous at the same time since you could have done actions that need some backup at the end, that's why the finally statement exists.
In addition you can model also a while statement without effectively using the condition of it:
while (!finished)
{
//do whatever
}
can become
try
{
while (true)
{
doSomethingThatEventuallyWillThrowAnException();
}
}
catch (Exception e)
{
//loop finished
}
A module developed by a partner caused our application to take a very long time to load. On closer examination, the module was looking for a config file at app startup. This by itself was not too objectionable, but the way in which it was doing it was outrageously bad:
For every file in the app directory, it opened the file and tried to parse it as XML. If a file threw an exception (because it wasn't XML), it caught the exception, squelched it, and tried the next file!
When the partner tested this module, they only had 3 files in the app directory. The bonehead config file search didn't have a noticeable effect on the test app startup. When we added it to our application, there were 100's of files in the app directory, and the app froze for nearly a minute at startup.
To add salt to the wound, the name of the config file the module was searching for was predetermined and constant. There was no need for a file search of any kind.
Genius has its limits. Stupidity is unbounded.
One example would be using exceptions to return a result from a recursive method:
public void Search(Node node, object data)
{
if(node.Data.Equals(data))
{
throw new ResultException(node);
}
else
{
Search(node.LeftChild, data);
Search(node.RightChild, data);
}
}
Doing something like this is a problem for several reasons.
It's completely counter-intuitive. Exceptions are designed for exceptional cases. Something working as intended should (we hope) never be an exceptional scenario.
You can't always rely on an exception being thrown and propagated to you. For example, if the exception-throwing code runs in a separate thread, you'll need some extra code to capture it.
It is a potential performance problem. There is an overhead associated with exceptions and if you throw a lot of them, you might see a performance drop in your application.
There are a few more examples and some interesting discussion on this subject here.
Disclaimer: The code above is adapted from the first sample on that wiki page to turn it into C#.