How can I clean up a long-running task before the process ends?

  • Thread starter Jeffrey Palermo, MCAD.Net
  • Start date
J

Jeffrey Palermo, MCAD.Net

I have some classes that control file processing in c#. The files come
from a mainframe and may take some time for each to process. I use the
Threadpool to process multiple files at once. Each instance of the
file processor is spawned in its own AppDomain to isolate it (I'm using
a separate AppDomain for a valid reason, so this thread isn't to
discuss that). My process is a Windows service, but I can duplicate
this problem as a regular console project.

When the process is terminated (or the Windows service stopped), any
files currently processing are stopped midway even though there is code
in my finally block. I've included the relavent classes below:
The main executable has these methods:
static void Main(string[] args) {
// call test method here.
RunController();
Console.WriteLine("hit enter to exit");
Console.ReadLine();
}
static void RunController(){
Controller ctrl = new Controller();
ctrl.DoWork();
}

And then the Controller class:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;

namespace ConsoleTestHarness
{
public class Controller
{
public Controller(){
// Just so we can see the activity.
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
}
public void DoWork(){
// Three might be running at the same time.
for(int i = 0; i < 3; i++){
ThreadPool.QueueUserWorkItem(new WaitCallback(Execute));
}
}
private void Execute(object state){
AppDomainSetup setup = null;
AppDomain domain = null;

try{
setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;

domain = AppDomain.CreateDomain("Processor", null, setup);

Processor proc =
(Processor)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
typeof(Processor).FullName);
proc.Process();
}finally{
if(domain != null)
AppDomain.Unload(domain);
}
}
}
}

And the processor class:

using System;
using System.Diagnostics;

namespace ConsoleTestHarness
{
[Serializable()]
public class Processor
{
public void Process(){
try{
Trace.WriteLine("Beginning Processing", this.ToString());
// Simulate a long-running process.
System.Threading.Thread.CurrentThread.Suspend();
}finally{
Trace.WriteLine("Finished", this.ToString());
// Close out database record an other cleanup.
}
}
}
}

I've stripped away irrelevant code. The code in the finally blocks
doesn't run, so files are left half-processed.

Has anyone else out there tackled a problem like this? Stopping a
service or terminating an EXE gracefully?


Best regards,
Jeffrey Palermo
http://www.jeffreypalermo.com
 
P

Peter Rilling

For a service you might be able to override the OnStop method. But it is
difficult to hook the system when a process if simply forced to terminate.

You might want to cleanup when the process starts rather then when it is
terminated. You can still do normal housecleaning upon normal termination,
but also cleanup when starting since you do not know how it was terminated.

Jeffrey Palermo said:
I have some classes that control file processing in c#. The files come
from a mainframe and may take some time for each to process. I use the
Threadpool to process multiple files at once. Each instance of the
file processor is spawned in its own AppDomain to isolate it (I'm using
a separate AppDomain for a valid reason, so this thread isn't to
discuss that). My process is a Windows service, but I can duplicate
this problem as a regular console project.

When the process is terminated (or the Windows service stopped), any
files currently processing are stopped midway even though there is code
in my finally block. I've included the relavent classes below:
The main executable has these methods:
static void Main(string[] args) {
// call test method here.
RunController();
Console.WriteLine("hit enter to exit");
Console.ReadLine();
}
static void RunController(){
Controller ctrl = new Controller();
ctrl.DoWork();
}

And then the Controller class:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;

namespace ConsoleTestHarness
{
public class Controller
{
public Controller(){
// Just so we can see the activity.
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
}
public void DoWork(){
// Three might be running at the same time.
for(int i = 0; i < 3; i++){
ThreadPool.QueueUserWorkItem(new WaitCallback(Execute));
}
}
private void Execute(object state){
AppDomainSetup setup = null;
AppDomain domain = null;

try{
setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;

domain = AppDomain.CreateDomain("Processor", null, setup);

Processor proc =
(Processor)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().Fu
llName,
typeof(Processor).FullName);
proc.Process();
}finally{
if(domain != null)
AppDomain.Unload(domain);
}
}
}
}

And the processor class:

using System;
using System.Diagnostics;

namespace ConsoleTestHarness
{
[Serializable()]
public class Processor
{
public void Process(){
try{
Trace.WriteLine("Beginning Processing", this.ToString());
// Simulate a long-running process.
System.Threading.Thread.CurrentThread.Suspend();
}finally{
Trace.WriteLine("Finished", this.ToString());
// Close out database record an other cleanup.
}
}
}
}

I've stripped away irrelevant code. The code in the finally blocks
doesn't run, so files are left half-processed.

Has anyone else out there tackled a problem like this? Stopping a
service or terminating an EXE gracefully?


Best regards,
Jeffrey Palermo
http://www.jeffreypalermo.com
 
J

Jeffrey Palermo, MCAD.Net

I'll post my solution to this problem so that anyone searching this
thread isn't left hanging.

I send work to the Threadpool, so when the Threadpool executes my
method, I add that thread to an ArrayList and remove it when finished.
That way I have a list of current threadpool thread I'm using. When I
need to abort, I just loop through them all and call Abort() and Join()
on each thread. It sends a ThreadAbortException down the thread, and
my normal cleanup code can execute. Join() blocks and waits until that
thread has exited before returning, so I delay the shutting down of my
service until all worker threads have exited. In this way, nothing is
left hanging. Here is my Controller class.

public class Controller
{
public ArrayList threads = new ArrayList();

public Controller(){
// Just so we can see the activity.
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
}
public void DoWork(){
try{
// Three might be running at the same time.
for(int i = 0; i < 3; i++){
ThreadPool.QueueUserWorkItem(new WaitCallback(Execute));
}
Thread.Sleep(20000);
Trace.WriteLine(threads.Count, this.ToString());
}finally{
foreach(Thread t in threads){
t.Abort();
t.Join();
}
}
}
private void Execute(object state){
threads.Add(Thread.CurrentThread);

AppDomainSetup setup = null;
AppDomain domain = null;

try{
setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;

domain = AppDomain.CreateDomain("Processor", null, setup);

Processor proc =
(Processor)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
typeof(Processor).FullName);
proc.Process();
}finally{
threads.Remove(Thread.CurrentThread);
if(domain != null)
AppDomain.Unload(domain);
}
}
}

Best regards,
Jeffrey Palermo
Blog: http://www.jeffreypalermo.com
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top