Think We have this part in our program:
private void button1_Click(object sender, EventArgs e)
{
while (true) ;
}
If we run the program it will crash and say "Not Responding". How to prevent this. I want the program check itself and if it dose not respond, restart itself.
Note:
We can use BackgroundWorker class. like this:
private readonly BackgroundWorker worker;
public Form1()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += Form1_Load;
worker.ProgressChanged += button1_Click;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
But there is no method or property or event in that class to understand not responding.
Any help will be appreciate.
Edit 3:
I used BackgroundWorker with ThredTimer and checked if process is "Not Responding". This dose not close the program but start a new one.
Certainly this is not a clean code, but it is a way. I'm still looking for how to use BeginInvoke as #micky said Or other way to make this beautiful. Thanks to all of you
private readonly BackgroundWorker _Worker;
public Form1()
{
InitializeComponent();
_Worker = new BackgroundWorker();
_Worker.DoWork += new DoWorkEventHandler(_Worker_DoWork);
_Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_Worker_RunWorkerCompleted);
_Worker.WorkerReportsProgress = true;
_Worker.WorkerSupportsCancellation = true;
}
// BCW starts here.
private void button1_Click(object sender, EventArgs e)
{
_Worker.RunWorkerAsync();
while (true) ;
}
// ThreadTimer will run here.
void _Worker_DoWork(object sender, DoWorkEventArgs e)
{
if (_Worker.IsBusy == true)
{
if (_Worker.CancellationPending)
{
e.Cancel = true;
}
TimerCallback tmrCallBack = new TimerCallback(CheckStatusThreadHealth);
System.Threading.Timer tmr = new System.Threading.Timer(tmrCallBack, null, 10000, 10000);
}
}
// This will call by ThreadTimer and check processes for not responding
public void CheckStatusThreadHealth(object IsBusy)
{
Process application = null;
foreach (var process in Process.GetProcesses())
{
if (process.ProcessName == "WindowsFormsApplication1")
{
application = process;
break;
}
}
if (!application.Responding)
{
// This should end BCW and go to _Worker_RunWorkerCompleted. But it didn't
_Worker.CancelAsync();
// This Restart program but did not close the last one.
Application.Restart();
}
}
// this should run when BCW has error or cancel, But never this happen
void _Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Application.Restart();
return;
}
}
The Answer: Thanks to Jason Williams
I used a watch dog program as a solution and Communicate between two program by this link The code Changed to this:
In Main Program
public partial class Form1 : Form
{
// Use SendMessage API
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hwnd, uint Msg, IntPtr wParam, IntPtr lParam);
// define a message
private const int RF_TESTMESSAGE = 0xA123;
// The timer will begin with the form
public Form1()
{
InitializeComponent();
var timer = new Timer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = 10000; //10 seconds
timer.Start();
}
//Timer look for a Watch Dog Program and send message to it
void timer_Tick(object sender, EventArgs e)
{
try
{
Process WatchDogProccess = Process.GetProcessesByName("Watch Dog")[0];
SendMessage(WatchDogProccess.MainWindowHandle, RF_TESTMESSAGE, IntPtr.Zero, IntPtr.Zero);
}
catch
{
//if Watch Dog is not running, will reach here
}
}
// if we click this button we will got "Not Responding"
private void button1_Click(object sender, EventArgs e)
{
while (true) ;
}
}
In Watch Dog Program
public partial class Form2 : Form
{
//the message that have been define in main program
private const int RF_TESTMESSAGE = 0xA123;
//Recive the message
protected override void WndProc(ref Message message)
{
if (message.Msg == RF_TESTMESSAGE)
{
// this mean the mian program run perfectly
}
else
{
//the main program is not responding
//Main program's name and address
string FileAddress = #"C:\Users\...\MainProgram.exe";
string FileName = "MainProgram";
// This will restart the main program
RestartProgram(FileAddress, FileName);
}
base.WndProc(ref message);
}
// This will restart the main program
public void RestartProgram(string FileAddress, string FileName)
{
//Find the Process
Process[] prs = Process.GetProcessesByName(FileName);
foreach (Process pr in prs)
{
if (!pr.Responding)
{
try
{
pr.Kill();
//then to restart-
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = FileAddress
}
};
process.Start();
}
catch { }
}
}
}
public Form2()
{
InitializeComponent();
}
}
}
You could solve this with a background thread that somehow detects that the ui thread is not responding. But how would this thread reset/restart your ui thread? This could be very difficult to do cleanly, as you would somehow have to reset all the state throughout your app to a known good condition.
So the conventional approach is instead to have a separate "watch dog" process. This can detect that your app is dead, kill its process and then run a new instance.
So how to detect that it is dead? There are many clever ways you can do this, but the simplest and most reliable is to just send a message from your app to the watch dog periodically (e.g. once per second) using a Windows forms timer - if your ui thread is busy it will not process the timer event and so the message won't be sent, and after a few seconds your watch dog will be confident that it is unresponsive, and be able to restart it.
Related
For all too long, I have been trying to run an external .bat file (calls an R script for some statistical processing), and have the console redirect to the U.I.
I think I am close, but just as I have gotten it to work I have run into a sizable problem! That is: it only bloody works once the main thread has ended (via: return;), and not during Thread.Sleep, or .WaitOne() or etc.
Here is my code in the main thread.
string batLoc = ALLRG___.RSCRBIN_LOC + "current.bat";
BackgroundWorker watchboxdWorker1 = new BackgroundWorker();
watchboxdWorker1.DoWork += frmC.WatchboxWorker1_WatchExt;
frmC.wbResetEvent = new AutoResetEvent(false);
watchboxdWorker1.RunWorkerAsync(batLoc);
//Thread.Sleep(1000*20);
//frmC.wbResetEvent.WaitOne();
return;
Note the commented out Sleep and/or WaitOne() instructions. If I try and use these the BackgroundWorker DOES execute, but the 'events' which update the U.I do not.
The code in my form (frmC above) is as follows,
public void WatchboxWorker1_WatchExt(object sender, DoWorkEventArgs e)
{
string exeLoc = (string) e.Argument;
string arg1 = exeLoc;
string arg2 = "";
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = exeLoc;
pStartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", arg1, arg2);
pStartInfo.WorkingDirectory = Path.GetDirectoryName(exeLoc);
pStartInfo.CreateNoWindow = true;
pStartInfo.UseShellExecute = false;
pStartInfo.RedirectStandardInput = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
Process process1 = new Process();
process1.EnableRaisingEvents = true;
process1.OutputDataReceived += new DataReceivedEventHandler(wbOutputHandler);
process1.ErrorDataReceived += new DataReceivedEventHandler(wbErrorHandler);
process1.StartInfo = pStartInfo;
process1.SynchronizingObject = rtbWatchbox;
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.StandardInput.Close();
process1.WaitForExit();
wbResetEvent.Set();
}
public void wbOutputHandler(Object source, DataReceivedEventArgs outLine)
{
int x = 0;
if (!String.IsNullOrEmpty(outLine.Data))
{
rtbWatchbox.AppendText(outLine.Data);
}
}
public void wbErrorHandler(Object source, DataReceivedEventArgs outLine)
{
int x = 0;
if (!String.IsNullOrEmpty(outLine.Data))
{
rtbWatchbox.AppendText(outLine.Data);
}
}
My problem is --
The wbOutputHandler and wbErrorHandler get fired as the console updates nicely - but only when the main thread has exited (using the return;).... if I use the Thread.Sleep or .WaitOne() in the main thread to pass control to the BackgroundWorker (WatchboxWorker1_WatchExt), then the code runs successfully, but the wbOutputHandler and wbErrorHandler methods do not get triggered at all.
In fact, if I do the Thread.Sleep(10*1000), then the external program starts running as planned, 10 seconds pass, then when the main UI thread exits I get a whole big enormous update all at once.
I don't want to have my main thread closed, I want to keep doing stuff there after the Worker is finished!
[ of course happy for alternate methods that are a better approach ]
"Help me Stack Overflow, you are my only hope!"
The answer was to put a backgroundWorker within another backgroundWorker, which is created for the UI Thread. I thought quite complicated given the reletivly simple requirement of printing a console output to the UI!
I now call my functions from the UI as follows -
private void btInsertBCModls_Click(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += RC2___Scratchpad4.BC_RunExistingBCModel;
bw.RunWorkerAsync(this);
}
Next I use the delegate & Invoke method on any richTextBox I need to update from another thread -
delegate void UpdateWriteboxCallback(String str);
public void wbWriteBox(string WriteString)
{
if (!String.IsNullOrEmpty(WriteString))
{
if (rtbWatchbox.InvokeRequired)
{
UpdateWriteboxCallback at = new UpdateWriteboxCallback(wbWriteBox);
this.Invoke(at, new object[] { WriteString });
}
else
{
// append richtextbox as required
}
}
}
Then from within my function I use another BackgroundWorker to run the console stuff -
public static void BC_RunExistingBCModel(object sender, DoWorkEventArgs e)
{
RC2___RhinegoldCoreForm frmC = e.Argument as RC2___RhinegoldCoreForm;
BackgroundWorker watchboxWorker = new BackgroundWorker();
watchboxWorker.DoWork += frmC.WatchboxWorker_RunProc;
watchboxWorker.RunWorkerAsync(batLoc);
while (watchboxWorker.IsBusy)
Thread.Sleep(50);
frmC.UpdateRGCoreStatusBox4("Executed script " + m + "... ");
}
Which in turn, in the DoWork function, calls the wbWriteBox function above.
public void WatchboxWorker_RunProc(object sender, DoWorkEventArgs e)
{
string exeLoc = (string) e.Argument;
string arg1 = exeLoc;
string arg2 = "";
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = exeLoc;
pStartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", arg1, arg2);
pStartInfo.WorkingDirectory = Path.GetDirectoryName(exeLoc);
pStartInfo.CreateNoWindow = true;
pStartInfo.UseShellExecute = false;
pStartInfo.RedirectStandardInput = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
Process process1 = new Process();
process1.EnableRaisingEvents = true;
process1.OutputDataReceived += (s, e1) => this.wbWriteBox(e1.Data);
process1.ErrorDataReceived += (s, e1) => this.wbWriteBox(e1.Data);
process1.StartInfo = pStartInfo;
process1.SynchronizingObject = rtbWatchbox;
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.StandardInput.Close();
process1.WaitForExit();
//wbResetEvent.Set();
}
Phew! A tricky solution to an easily defined problem. If someone has a better way, let me know.
And thanks to Carsten for all the help - magnificent.
I am struggling with threading.
The problem is when I am iterating trough foreach loop.
When setting this.Document, the application performs login, that is triggered with an event and takes few seconds to complete. In the worker_RunWorkerCompleted method I need to perform some actions that depend on current login information.
The problem is that before I can perform this action for the first file, the this.Document already changes making the application perform another login. This way I can never actually perform my actions.
My question is: How can I pause the next thread until previous thread has completed.
Is there any other solution to my problem?
I tried with AutoResetEvent but I got no luck. I set waitOne() just after the RunWorkerAsync call and .Set() in the RunWorkerCompleted. The code never gets to RunWorkerCompleted...
Here is the code:
public void Start(object obj)
{
try
{
foreach (KeyValuePair<string, Stream> pair in this.CollectionOfFiles)
{
Worker = new BackgroundWorker();
Worker.DoWork += new DoWorkEventHandler(worker_DoWork);
Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
using (Stream stream = pair.Value)
{
primaryDocument = new Document(stream);
DataHolderClass dataHolder = new DataHolderClass();
dataHolder.FileName = pair.Key;
dataHolder.Doc = secondaryDocument;
//background thread call
Worker.RunWorkerAsync(dataHolder);
}
}
}
catch (Exception ex)
{
// exception logic
}
finally
{
// complete logic
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
DataHolderClass dataHolder = ((DataHolderClass)e.Argument);
// setting this attribute triggers execution of login event
this.Document = dataHolder.Doc;
e.Result = (dataHolder);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// here I need to perform some actions that are depending on the current login
DataHolderClass dataHolder = ((DataHolderClass)e.Result);
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Info) { Title = dataHolder.FileName });
}
no9,
Try the following:
System.Threading.ManualResetEvent _busy = new System.Threading.ManualResetEvent(false);
void ResumeWorker()
{
// Start the worker if it isn't running
if (!backgroundWorker1.IsBusy) backgroundWorker1.RunWorkerAsync(dataHolder);
// Unblock the worker
_busy.Set();
}
void PauseWorker()
{
// Block the worker
_busy.Reset();
}
void CancelWorker()
{
if (backgroundWorker1.IsBusy) {
// Set CancellationPending property to true
backgroundWorker1.CancelAsync();
// Unblock worker so it can see that
_busy.Set();
}
}
then in your code run the method.
Let me know if it works :)
class SimpleWaitPulse
{
static readonly object _locker = new object();
static bool _go;
static void Main()
{ // The new thread will block
new Thread (Work).Start(); // because _go==false.
Console.ReadLine(); // Wait for user to hit Enter
lock (_locker) // Let's now wake up the thread by
{ // setting _go=true and pulsing.
_go = true;
Monitor.Pulse (_locker);
}
}
static void Work()
{
lock (_locker)
while (!_go)
Monitor.Wait (_locker); // Lock is released while we’re waiting
Console.WriteLine ("Woken!!!");
}
}
Can you use pulse ?
Taken from : Threading in C# from albahari.com
Well, the design is terrible... but if you need to stick to it, you can set wait handles in a previous worker and wait for it in next. This is the minimal fix, still quite an abomination:
public void Start(object obj)
{
try
{
BackgroundWorker previousWorker = null;
DataHolderClass previousWorkerParams = null;
foreach (KeyValuePair<string, Stream> pair in this.CollectionOfFiles)
{
// signal event on previous worker RunWorkerCompleted event
AutoResetEvent waitUntilCompleted = null;
if (previousWorker != null)
{
waitUntilCompleted = new AutoResetEvent(false);
previousWorker.RunWorkerCompleted += (o, e) => waitUntilCompleted.Set();
// start the previous worker
previousWorker.RunWorkerAsync(previousWorkerParams);
}
Worker = new BackgroundWorker();
Worker.DoWork += (o, e) =>
{
// wait for the handle, if there is anything to wait for
if (waitUntilCompleted != null)
{
waitUntilCompleted.WaitOne();
waitUntilCompleted.Dispose();
}
worker_DoWork(o, e);
};
using (Stream stream = pair.Value)
{
primaryDocument = new Document(stream);
DataHolderClass dataHolder = new DataHolderClass();
dataHolder.FileName = pair.Key;
dataHolder.Doc = secondaryDocument;
// defer running this worker; we don't want it to finish
// before adding additional completed handler
previousWorkerParams = dataHolder;
}
previousWorker = Worker;
}
if (previousWorker != null)
{
previousWorker.RunWorkerAsync(previousWorkerParams);
}
}
catch (Exception ex)
{
// exception logic
}
finally
{
// complete logic
}
}
This is the code I use to record an audio file:
internal class AudioRecorder
{
public WaveIn waveSource = null;
public WaveFileWriter waveFile = null;
public string RECORDING_PATH;
public AudioRecorder(string fileName)
{
RECORDING_PATH = fileName;
}
public void Start()
{
waveSource = new WaveIn();
waveSource.WaveFormat = new WaveFormat(44100, 1);
waveSource.DeviceNumber = 0;
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);
waveFile = new WaveFileWriter(RECORDING_PATH, waveSource.WaveFormat);
System.Timers.Timer t = new System.Timers.Timer(30000);
t.Elapsed += new ElapsedEventHandler(Stop);
waveSource.StartRecording();
t.Start();
}
private void Stop(object sender, ElapsedEventArgs args)
{
waveSource.StopRecording();
}
private void waveSource_DataAvailable(object sender, WaveInEventArgs e)
{
if (waveFile != null)
{
waveFile.Write(e.Buffer, 0, e.BytesRecorded);
waveFile.Flush();
}
}
private void waveSource_RecordingStopped(object sender, StoppedEventArgs e)
{
if (waveSource != null)
{
waveSource.Dispose();
waveSource = null;
}
if (waveFile != null)
{
waveFile.Dispose();
waveFile = null;
}
}
}
In the main method I do:
AudioRecorder r = new AudioRecorder(dialog.FileName);
r.Start();
FileInfo file = new FileInfo(r.RECORDING_PATH);
// Do somehting with the recorded audio //
The problem is that when I do r.Start() the thread does not block and keeps running. So I get a corrupt file error. When I try things like Thread.Sleep to keep the thread waiting until recording finishes, this time the AudioRecorder code does not work well (i.e. recording never finishes).
Any ideas about what should I do to correctly wait the recording to finish so that I can safely use the recorded file ?
If you want to record for 30 seconds exactly, just call StopRecording in the DataAvailable event handler once you have enough data. There is absolutely no need for a complicated threading strategy. I do exactly this in the open source .NET voice recorder application.
Dispose the WaveFileWriter in the RecordingStopped event.
If you absolutely must have a blocking call, then use WaveInEvent, and wait on an event which is set in the RecordingStopped handler, as suggested by Rene. By using WaveInEvent, you remove the need for windows message pump to be operational.
You use a ManualResetEvent to wait for the Stop event to be called, giving other threads a change to proceed.
I've only added the new bits...
internal class AudioRecorder
{
private ManualResetEvent mre = new ManualResetEvent(false);
public void Start()
{
t.Start();
while (!mre.WaitOne(200))
{
// NAudio requires the windows message pump to be operational
// this works but you better raise an event
Application.DoEvents();
}
}
private void Stop(object sender, ElapsedEventArgs args)
{
// better: raise an event from here!
waveSource.StopRecording();
}
private void waveSource_RecordingStopped(object sender, EventArgs e)
{
/// ... your code here
mre.Set(); // signal thread we're done!
}
It is good idea to avoid any multi-threaded code if it is not required and Mark's answer is explaining this perfectly.
However, if you are writing a windows application and the requirement is to record 30 seconds than it is a must not to block a main thread in waiting (for 30 seconds). The new async C# feature can be very handy here. It will allow you to keep code logic straightforward and implement waiting in a very efficient way.
I have modified your code slightly to show how the async feature can be used in this case.
Here is the Record method:
public async Task RecordFixedTime(TimeSpan span)
{
waveSource = new WaveIn {WaveFormat = new WaveFormat(44100, 1), DeviceNumber = 0};
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);
waveFile = new WaveFileWriter(RECORDING_PATH, waveSource.WaveFormat);
waveSource.StartRecording();
await Task.Delay(span);
waveSource.StopRecording();
}
Example of using Record from click handler of WPF app:
private async void btnRecord_Click(object sender, RoutedEventArgs e)
{
try
{
btnRecord.IsEnabled = false;
var fileName = Path.GetTempFileName() + ".wav";
var recorder = new AudioRecorder(fileName);
await recorder.RecordFixedTime(TimeSpan.FromSeconds(5));
Process.Start(fileName);
}
finally
{
btnRecord.IsEnabled = true;
}
}
However, you have to watch out for timing here. Task.Delay does not guarantee that it will continue execution after the exact specified time span. You might get records slightly longer than is required.
I have a method that allows a word document to be opened in word and waits for word to exit before completing.
If word is not already running all works well.
If word is running the process exits immediately so I can;t wait for exit.
Any ideas how I can wait for the document to close if word is already running?
This is on Windows 8.1
public void ShowExternalReference(string externalRef, bool waitForCompletion)
{
if (externalRef.NotEmpty())
{
var pInfo = new ProcessStartInfo {FileName = externalRef};
// Start the process.
Process p = Process.Start(pInfo);
if (waitForCompletion)
{
// Wait for the window to finish loading.
p.WaitForInputIdle();
// Wait for the process to end.
p.WaitForExit();
}
}
}
you can get the current running Word Process and attach to the events
I got some info here
Here is an example for attaching and putting text into the doc...
Hope this helps.
using Word = Microsoft.Office.Interop.Word;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load_1(object sender, EventArgs e)
{
}
public void ShowExternalReference(string externalRef, bool waitForCompletion)
{
if (externalRef.Length > 0)
{
var pInfo = new ProcessStartInfo { FileName = externalRef };
bool isrunning = false;
Process [] pList = Process.GetProcesses();
foreach(Process x in pList)
{
if( x.ProcessName.Contains("WINWORD"))
{
isrunning = true;
Word.Application myWordApp =
System.Runtime.InteropServices.Marshal.GetActiveObject(
"Word.Application") as Word.Application;
if(myWordApp.ActiveDocument.FullName.Contains(externalRef))
// do something
myWordApp.ActiveDocument.Content.Text = " already open";
}
}
if(!isrunning)
{
// Start the process.
Process p = Process.Start(pInfo);
if (waitForCompletion)
{
// Wait for the window to finish loading.
p.WaitForInputIdle();
// Wait for the process to end.
p.WaitForExit();
}
}
}
}
private void button1_Click(object sender, EventArgs e)
{
string myWordFile = #"C:\Temp\test.docx";
ShowExternalReference(myWordFile, true);
}
private void listView1_ItemChecked(object sender, ItemCheckEventArgs e)
{
listView1.Items[e.Index].Group = listView1.Groups[e.NewValue == CheckState.Checked ? 0 : 1];
}
Process[] pname = Process.GetProcessesByName("winword.exe");
if(pname.Length == 0)
{
//not running..
}
you could loop this through a background thread perhaps to continually test if Word is running, and fire an event back when it isn't
I have a c# method in console app X this starts a process; console app Y (written in the same c# solution).
App Y then fires a vba macro in an Excel 2010 workbook.
For testing purposes in the wkbook VBA I've added some code to force a runtime error 1004.
The winForm uses a process event, triggered using a Forms timer, to kill the Process. It is working as programmed I'd just like to try to make it do a little more.
Why, when I kill the process, is the instance of XL staying open at the point when it finds the error? How do I find a way of getting rid of the instance of XL, if it still exists, when it kills the process, and then posting an error message back to my winForm?
(ps the following code is familiar but the question is not a duplicate)
private int elapsedTime;
private Process p;
private System.Windows.Forms.Timer myTimer;
const int SLEEP_AMOUNT = 1000;//1s
const int MAXIMUM_EXECUTION_TIME = 5000;//5s
private void btRunReport_Click(object sender, EventArgs e) {
btRunReport.Enabled = false;
lbStatusUpdate.Text = "Processing..";
//instantiate a new process and set up an event for when it exits
p = new Process();
p.Exited += new EventHandler(MyProcessExited);
p.EnableRaisingEvents = true;
p.SynchronizingObject = this;
elapsedTime = 0;
this.RunReportScheduler();
//add in a forms timer so that the process can be killed after a certain amount of time
myTimer = new System.Windows.Forms.Timer();
myTimer.Interval = SLEEP_AMOUNT;
myTimer.Tick += new EventHandler(TimerTickEvent);
myTimer.Start();
}
private void RunReportScheduler() {
p.StartInfo.FileName = #"\\fileserve\department$\ReportScheduler_v3.exe";
p.StartInfo.Arguments = 2;
p.Start();
}
private void MyProcessExited(Object source, EventArgs e){
myTimer.Stop();
btRunReport.Enabled = true;
lbStatusUpdate.Text = "Start";
}
void TimerTickEvent(Object myObject, EventArgs myEventArgs) {
myTimer.Stop();
elapsedTime += SLEEP_AMOUNT;
if (elapsedTime > MAXIMUM_EXECUTION_TIME)
{p.Kill();}
else
{myTimer.Start();}
}
it might be the issue with report scheduler which does not have the proper method which closes Excel.
This is such method:
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Exception Occured while releasing object " + ex.ToString());
}
finally
{
GC.Collect();
}
}
I've left the original bit of code unchanged but I've used the help from Andrew but mainly the help of a good friend of mine who unfortunately isn't signed up to SO. Excel seems to be dead!. Plus he's coded it in such a way that it passes back an indicator telling the form if it had problems with excel or not. Also gives us the option of building in maximum run times for each excel process.
He used the following SO answer to help get rid of Excel
1.In scheduler program
Move timer there
Implement excel cleaning code in the case there is no errors in vba and in the opposite case when maximum execution time reached (use Kill method)
From the scheduler return 0 to the forms application if excel finished normally or 1 if it was killed
2.In the forms application analyse return value from the scheduler in the ProcessExited event handler and enable button, etc
So, the new scheduler:
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Excel = Microsoft.Office.Interop.Excel;
using System.Timers;
class Program
{
private const int SLEEP_AMOUNT = 1000;
private const int MAXIMUM_EXECUTION_TIME = 10000;
private Excel.Application excelApp =null;
private Excel.Workbook book =null;
private Timer myTimer;
private int elapsedTime;
private int exitCode=0;
[DllImport("user32.dll", SetLastError =true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd,out uint lpdwProcessId);
static int Main(string[] args)
{
Program myProgram = newProgram();
myProgram.RunExcelReporting(1);
return myProgram.exitCode;
}
void myTimer_Elapsed(object sender,ElapsedEventArgs e)
{
myTimer.Stop();
elapsedTime += SLEEP_AMOUNT;
if (elapsedTime > MAXIMUM_EXECUTION_TIME)
{
//error in vba or maximum time reached. abort excel and return 1 to the calling windows forms application
GC.Collect();
GC.WaitForPendingFinalizers();
if (book != null)
{
book.Close(false,Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(book);
book =null;
}
if (excelApp != null)
{
int hWnd = excelApp.Hwnd;
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd,out processID);
if (processID != 0)
Process.GetProcessById((int)processID).Kill();
excelApp =null;
exitCode = 1;
}
}
else
{
myTimer.Start();
}
}
void RunExcelReporting(int x)
{
myTimer =new Timer(SLEEP_AMOUNT);
elapsedTime = 0;
myTimer.Elapsed +=new ElapsedEventHandler(myTimer_Elapsed);
myTimer.Start();
try{
excelApp =new Excel.Application();
excelApp.Visible =true;
book = excelApp.Workbooks.Open(#"c:\jsauto.xlsm");
excelApp.Run("ThisWorkbook.rr");
book.Close(false,Type.Missing, Type.Missing);
}
catch (Exception ex){
Console.WriteLine(ex.ToString());
}
finally
{
//no error in vba and maximum time is not reached. clear excel normally
GC.Collect();
GC.WaitForPendingFinalizers();
if (book != null)
{
try {
book.Close(false,Type.Missing, Type.Missing);
}
catch { }
Marshal.FinalReleaseComObject(book);
}
if (excelApp != null)
{
excelApp.Quit();
Marshal.FinalReleaseComObject(excelApp);
excelApp =null;
}
}
}
}
And the new forms application:
public partial class Form1 : Form
{
SqlDataAdapter myAdapt = null;
DataSet mySet =null;
DataTable myTable =null;
public Form1()
{ InitializeComponent();}
privatevoid Form1_Load(object sender,EventArgs e){
InitializeGridView();
}
private Process myProcess;
private void btRunProcessAndRefresh_Click(object sender,EventArgs e)
{
myProcess =new Process();
myProcess.StartInfo.FileName =#"c:\VS2010Projects\ConsoleApplication2\ConsoleApplication4\bin\Debug\ConsoleApplication4.exe";
myProcess.Exited +=new EventHandler(MyProcessExited);
myProcess.EnableRaisingEvents =true;
myProcess.SynchronizingObject =this;
btRunProcessAndRefresh.Enabled =false;
myProcess.Start();
}
privatevoid MyProcessExited(Object source,EventArgs e)
{
InitializeGridView();
btRunProcessAndRefresh.Enabled =true;
if (((Process)source).ExitCode == 1)
{
MessageBox.Show("Excel was aborted");
}
else
{
MessageBox.Show("Excel finished normally");
}
}
private void btnALWAYSWORKS_Click(object sender,EventArgs e) {
InitializeGridView();
}
privatevoid InitializeGridView() {
using (SqlConnection conn =new SqlConnection(#"Data Source=sqliom3;Integrated Security=SSPI;Initial Catalog=CCL"))
{
myAdapt =new SqlDataAdapter("SELECT convert(varchar(25),getdate(),120) CurrentDate", conn);
mySet =new DataSet();
myAdapt.Fill(mySet,"AvailableValues");
myTable = mySet.Tables["AvailableValues"];
this.dataGridViewControlTable.DataSource = myTable;
this.dataGridViewControlTable.AllowUserToOrderColumns =true;
this.dataGridViewControlTable.Refresh();
}
}
}