Why does BackgroundWorker.RunWorkerCompleted +=OnRunWorkerCompleted event not get called?

Jane Jie Chen 466 Reputation points
2024-12-10T03:23:07.0766667+00:00

Our application uses the WPF and .NET Framwork 4.8

The AbortableBackgroundWorkder class inherences from the BackgroundWorker class and used as following.

ProtocolProcessor class performs complicated sequence instrument control actions.

we notice that sometimes due to instrument control error and console application Abort _protocolProcessorBackgroundWorker and event "OnProtocolProcessorRunEvent()" did not get called.

if the event "OnProtocolProcessorRunEvent()" is called, it will show a dialog to indicate there is a instrument error.

when instrument error happens, it called "GenerateInstrumentExceptionWarningOrFault()" event and write several lines into the run log file.

when ProtocolProcessor::Execute() finally block, it writes several lines into run log file.

when the instrument error dialog did not show, we notice that "GenerateInstrumentExceptionWarningOrFault()" event wrote several lines and mixed with writhed lines from ProtocolProcessor::Execute() finally block.

if the code can keep all wrote lines from "GenerateInstrumentExceptionWarningOrFault()" event and after that, it shows wrote lines from ProtocolProcessor::Execute() finally block, the instrument error dialog will display.

Through the code example, are there any problems?

Is this safe to add "Thread.Sleep"? Thx!

public class ProtocolProcessor
{
	public void Execute()       
	{
		try
        {
            //  Raise (and log) the Macro Started event
            MarkMacroStarted();
			
			// a lot instrument control sequence actions
			.......

			EvaluateMacroStopPoint( "Macro Finished" );
		}
		catch( Exception ex )
        {
            WriteToRunLog( "!!! Exception Occurred in Macro !!! - " + ex.Message );
            stoppedByError = true;
            throw;
        }
        finally
        {
			// Thread.Sleep(200);  //is it safe to sleep in finally block?
            //  handle case where the macro was stopped by the user (not aborted)
            //  exception occurred, was logged and thrown again
			TurnOffAllHeatersAndStopLogging();  //include write sth into run log
            
		}
	}
}
private AbortableBackgroundWorker           _protocolProcessorBackgroundWorker  = null;
private ProtocolProcessor                   _protocolProcessorMacro             = null;

Public void BeginXXXRun()
{
	InstrumentControl.Instance.ExceptionThrown += OnInstrumentControlExceptionThrownDuringProtocol;

	_protocolProcessorBackgroundWorker = new AbortableBackgroundWorker();
    _protocolProcessorBackgroundWorker.WorkerSupportsCancellation = true;
    _protocolProcessorBackgroundWorker.DoWork += (_, __) =>
    {
		try
		{
		_protocolProcessorMacro = new ProtocolProcessor( );
        _protocolProcessorMacro.ProtocolProcessorRunEvent += OnProtocolProcessorRunEvent;
		
		_protocolProcessorMacro.PreExecute();
        _protocolProcessorMacro.Execute();
        _protocolProcessorMacro.PostExecute();
		}
		catch ( ThreadAbortException /* taEx */ )
        {
        }
        catch ( Exception ex )
        {
             GenerateInstrumentExceptionWarningOrFault( ex );
             throw;
        }
        finally
        {
        
               _protocolProcessorMacro.StopLoggingToFile();
        }
	};
	_protocolProcessorBackgroundWorker.RunWorkerCompleted += OnProtocolProcessorRunComplete;
    _protocolProcessorBackgroundWorker.RunWorkerAsync();
  }
}

private void OnInstrumentControlExceptionThrownDuringProtocol( object sender, InstrumentControl.ExceptionThrownEventArgs args )
{
	 BioCodeException bioEx = args.InstrumentException;
	
	string abortedBy = "Instrument Error";
    WriteToRunLog( "!!! AbortRun( " + abortedBy + " )"  );  //write into run log

	_protocolProcessorBackgroundWorker.Abort(abortedBy);
	
    LogInstrumentExceptionInRunLog( bioEx );                   //  write logs in run log
    GenerateInstrumentExceptionWarningOrFault( bioEx );        //  saving warning or error into a file
}

private void LogInstrumentExceptionInRunLog( Exception ex )
{
    string message = "!!! " + ex.Message;
 
}

private void GenerateInstrumentExceptionWarningOrFault( Exception ex )
        {
            WriteToRunLog( "**** !! Exception - " + ex.Message ); //write into run log
            
            BioCodeException bioEx = PackageBioCodeException( ex );
            
            EventLogger eventLogger = EventLogger.Instance;
            if ( eventLogger != null )
            {
                eventLogger.GenerateFaultLog( bioEx );  //generate event log file
            }
        }

private void OnProtocolProcessorRunComplete( object sender, RunWorkerCompletedEventArgs e )
{
	bool    wasAborted  = e.Cancelled;
    string abortedBy;
	if ( wasAborted )
    {
        abortedBy = _protocolProcessorBackgroundWorker.AbortedBy.ToString();
		HaltInstrumentOnBackgroundThread();
    }
	if ( _protocolProcessorMacro != null )
    {
        _protocolProcessorMacro.ProtocolProcessorRunEvent -= OnProtocolProcessorRunEvent;
        _protocolProcessorMacro = null;
    }

	RaiseProtocolProcessorRunEvent( "", PROTOCOLFINISH, new string[] { abortedBy } );
}
 ///-----------------------------------------------------------------------------------------------------------------
    /// Class (AbortableBackgroundWorker)
    ///-----------------------------------------------------------------------------------------------------------------
    public class AbortableBackgroundWorker : BackgroundWorker
    {
        ///-------------------------------------------------------------------------------------------------------------
        /// Properties
        ///-------------------------------------------------------------------------------------------------------------
        public string AbortedBy { get; set; }
        public bool   IsAlive   { get { return _workerThread != null && _workerThread.IsAlive; } }
        ///-------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Handles DoWork event
        /// </summary>
        ///-------------------------------------------------------------------------------------------------------------
        protected override void OnDoWork( DoWorkEventArgs args )
        {
            // get the thread from the base class
            _workerThread = Thread.CurrentThread;
            try
            {
                base.OnDoWork( args );
            }
            catch( ThreadAbortException )
            {
                args.Cancel = true;         // we must set Cancel property to true!
                Thread.ResetAbort();        // prevents ThreadAbortException propagation
            }
        }
        [SecurityPermissionAttribute( SecurityAction.Demand, ControlThread = true )]
        ///-------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Aborts the background worker.
        /// </summary>
        ///-------------------------------------------------------------------------------------------------------------
        public void Abort()
        {
            Abort( "" );
        }
        [SecurityPermissionAttribute( SecurityAction.Demand, ControlThread = true )]
        ///-------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Aborts the background worker.
        /// </summary>
        ///-------------------------------------------------------------------------------------------------------------
        public void Abort( object state = null )
        {
            AbortedBy = state.ToString();
            if ( _workerThread != null )
            {
                _workerThread.Abort( state );
                _workerThread = null;
            }
        }
        [SecurityPermissionAttribute( SecurityAction.Demand, ControlThread = true )]
        ///-------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Aborts the background worker.
        /// </summary>
        ///-------------------------------------------------------------------------------------------------------------
        public bool Join( int timeout )
        {
            if ( _workerThread != null )
            {
                return _workerThread.Join( timeout );
            }
            else
            {
                return true;
            }
        }
        ///-------------------------------------------------------------------------------------------------------------
        /// Member Variables
        ///-------------------------------------------------------------------------------------------------------------
        private Thread _workerThread = null;
    }
.NET
.NET
Microsoft Technologies based on the .NET software framework.
4,007 questions
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,804 questions
0 comments No comments
{count} votes

Accepted answer
  1. Jiale Xue - MSFT 48,441 Reputation points Microsoft Vendor
    2024-12-10T08:18:48.13+00:00

    Hi @Jane Jie Chen , Welcome to Microsoft Q&A,

    After terminating the thread through Thread.Abort in AbortableBackgroundWorker, the subsequent operations of DoWork may be skipped directly, which may cause some events (such as OnProtocolProcessorRunEvent) not to be triggered.

    Thread.Abort is not a recommended way to terminate a thread because it may cause unpredictable behavior, such as skipping some critical code blocks (including finally blocks).

    The reason for the confusion of log lines may be that multiple threads compete to write to the same log file or stream. Even if the log writing occurs in order, the output order may be inconsistent due to asynchronous behavior.

    Using Thread.Sleep in the finally block is generally not a good choice, especially in a multi-threaded environment, it may cause unnecessary thread blocking and affect overall performance.

    If you need to wait for some resources or operations to complete, use Task.Delay or wait for event signals instead of Thread.Sleep.

    Best Regards,

    Jiale


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". 

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.