Link to home
Start Free TrialLog in
Avatar of pclarke7
pclarke7

asked on

Background Worker Thread and Global Variables

I have a WCF Service written in C# which makes extensive user of global variables. The Service runs menus and transaction which are made up of sequenced database records with each database record capable of calling specific logic based on inbuilt commands. A typical simplistic example could be:

  05  Start
  10  Display Screen
  20  Input Range of Items
  30  run SQL statement to retrieve item details from Item master table
  40  Display Item Details
  50  Confirm selected Items
*60  Run SQL statement to report on Item Usage
*70  Output SQL results to CSV  
*80  Email usage report to user
  90  Goto Start

Those marked with * should run in a background thread so that after seq 50 the user will be returned to seq 05 whilst the background thread takes over. The issue I have is that Many of the variables controlling the transactions are global variables and so when the user returns to 05 and then exists to a new transactions this effects the background job since the background job is running the same code as the UI , except in the background thread.

I have copied all of the variables from UI to the background thread however the UI continues to update these global background thread variables after the background thread is launched. How can I stop the UI from affecting the background thread global variables ?

Pat
Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

To be blunt change the design and don't use global variables, it is bad practice.
Hi pclarke7;

I am in total agreement with @Andy but with that said if I had to complete this project in its current state this is what I would do.

  1. Create a class that has the variables that are in the global namespace that will be used in the BackgroundWorker
  2. Create and instance of that class and copy over the variables from global namespace if a variable is a ref type you will need to create a new object for that variable and not copy it
  3. When you call the BackgroundWorker pass in this object as a parameter
  4. In the BackgroundWorker use the values from the object you just passed in.

At this point the changes made to the global namespace will not effect the BackgroundWorker because it has its own values.
Avatar of pclarke7
pclarke7

ASKER

Hi Andy & Fernando,

Point 1 - Use of Globals
I'm relatively new to c# and so interested to find out why it's considered bad practice to use global variables. I have used them extensively in this project and my reasons for doing so (rightly or wrongly) was to make the classes & variables available to the various .cs files within the project. Without using globals I believe that I would have to copy each of the classes and variables to each .cs file.

below is the list of globals within this project
    public class globals
    {
        public globals g;
        public TransactionService MyTransactionService;
        public UserMaster MyUser;
        public RegDetails MyRegDetails;
        public EnterpriseMaster MyEnterprise;
        public TransactionHdr MyTransHdr;
        public TransactionSeq MyTransSeq;
        public TransactionsWaitingToLoad MyTransWaiting;
        public ExecuteTrans MyExe;
        public Commands MyCommands;
        public ErrorMsgs MyErrMsgs;
        public PromptMsgs MyPromptMsgs;
        public ConnectionStrings MyConnString;
        public Environments MyEnvironments;
        public RuleTypeOpSeq MyRuleTypeOpSeq;
        public LogMessages MyLogMessages;
        public DataBaseIO MyDataBaseIO;
        public ErrorsEncountered MyErrorsEncountered;
        public DisplaySeq MyDisplaySeq;
        public PassToServiceParms MyPassToServiceParms;
        public RtnFromServiceParms MyRtnFromServiceParms;
        public InputValues MyInpFldVal;
        public Object MyVarObj;
        public NextNumbers MyNextNumbers;
        public TransOutput MyTransOutput;
        public Invitation MyInvitation;

        public AppDistLst MyAppDistLst;

        public List<TransactionHdr> MyTransHdrList;
        public List<TransactionSeq> MyTransSeqList;
        public List<Environments> MyEnvironmentList;
        public List<ErrorMsgs> MyErrMsgsList;
        public List<PromptMsgs> MyPromptMsgsList;
        public List<ConnectionStrings> MyConnStringList;
        public List<DisplaySeq> MyDisplaySeqList;
        public List<TransactionsWaitingToLoad> MyTransWaitingList;
        public List<ErrorsEncountered> MyErrorsEncounteredList ;
        public List<InputValues> MyInpFldValList ;
        

        public Dictionary<string, int> MyTagsDict;
        public Dictionary<string, int> MyCharDict;
        public Dictionary<string, string> MyVarValuesDict;
        public Dictionary<string, string> MyVarTypesDict;
        public Dictionary<string, string> MyCondLvlsDict;
        public Dictionary<string, string> MyRuleTypeOpSeqDict;
        public Dictionary<string, string> MyLogMessagesDict;
        public Dictionary<string, string> MyKeywordsDict;
        public Dictionary<string, int[]> MyPrevSelDict;
        public Dictionary<string, object> MyObjDict ;
        public Dictionary<string, string> MyAppDistLstDict;
        public Dictionary<string, string> MyNextNumbersDict;

        public TransLvlValues MyTransLvlValues;
        public SessionLvlValues MySessionLvlValues;
        public AppLvlValues MyAppLvlValues;



       // Create global objects
	//	 
public TransactionService CrtTransactionService()
        {
            MyTransactionService = new TransactionService();
            return MyTransactionService;
        }       


        public UserMaster CrtUserMaster()
        {
            MyUser = new UserMaster();
            return MyUser;
        }
        public RegDetails CrtRegDetails()
        {
            MyRegDetails = new RegDetails();
            return MyRegDetails;
        }
        public EnterpriseMaster CrtEnterpriseMaster()
        {
            MyEnterprise = new EnterpriseMaster();
            return MyEnterprise;
        }
        public TransactionHdr CrtTransactionHdr()
        {
            MyTransHdr = new TransactionHdr();
            return MyTransHdr;
        }

        public TransactionSeq CrtTransactionSeq()
        {
            MyTransSeq = new TransactionSeq();
            return MyTransSeq;
        }

        public TransactionsWaitingToLoad CrtTransactionsWaitingToLoad()
        {
            MyTransWaiting = new TransactionsWaitingToLoad();
            return MyTransWaiting;
        }

        public ExecuteTrans CrtExecuteTrans()
        {
            MyExe = new ExecuteTrans();
            return MyExe;
        }

        public Commands CrtCommands()
        {
            MyCommands = new Commands();
            return MyCommands;
        }

        public ErrorMsgs CrtErrorMsgs()
        {
            MyErrMsgs = new ErrorMsgs();
            return MyErrMsgs;
        }

        public PromptMsgs CrtPromptMsgs()
        {
            MyPromptMsgs = new PromptMsgs();
            return MyPromptMsgs;
        }

        public ConnectionStrings CrtConnectionStrings()
        {
            MyConnString = new ConnectionStrings();
            return MyConnString;
        }

        public Environments CrtEnvironments()
        {
            MyEnvironments = new Environments();
            return MyEnvironments;
        }

        public RuleTypeOpSeq CrtRuleTypeOpSeq()
        {
            MyRuleTypeOpSeq = new RuleTypeOpSeq();
            return MyRuleTypeOpSeq;
        }

        public LogMessages CrtLogMessages()
        {
            MyLogMessages = new LogMessages();
            return MyLogMessages;
        }

        public DataBaseIO CrtDataBaseIO()
        {
            MyDataBaseIO = new DataBaseIO();
            return MyDataBaseIO;
        }

        public ErrorsEncountered CrtErrorsEncountered()
        {
            MyErrorsEncountered = new ErrorsEncountered();
            return MyErrorsEncountered;
        }
        
        public DisplaySeq CrtDisplaySeq()
        {
            MyDisplaySeq = new DisplaySeq();
            return MyDisplaySeq;
        }

        public PassToServiceParms CrtPassToServiceParms()
        {
            MyPassToServiceParms = new PassToServiceParms();
            return MyPassToServiceParms;
        }

        public RtnFromServiceParms CrtRtnFromServiceParms()
        {
            MyRtnFromServiceParms = new RtnFromServiceParms();
            return MyRtnFromServiceParms;
        }
       
        public InputValues CrtInputValues()
        {
            MyInpFldVal = new InputValues();
            return MyInpFldVal;
        }
        public Object CrtVarObj()
        {
            MyVarObj = new Object();
            return MyVarObj;
        }
        public NextNumbers CrtNextNumbers()
        {
            MyNextNumbers = new NextNumbers();
            return MyNextNumbers;
        }
        public AppDistLst CrtAppDistLst()
        {
            MyAppDistLst = new AppDistLst();
            return MyAppDistLst;
        }
        public TransLvlValues CrtTransLvlValues()
        {
            MyTransLvlValues = new TransLvlValues();
            return MyTransLvlValues;
        }
        public SessionLvlValues CrtSessionLvlValues()
        {
            MySessionLvlValues = new SessionLvlValues();
            return MySessionLvlValues;
        }
        public AppLvlValues CrtAppLvlValues()
        {
            MyAppLvlValues = new AppLvlValues();
            return MyAppLvlValues;
        }
        public TransOutput CrtTransOutput()
        {
            MyTransOutput = new TransOutput();
            return MyTransOutput;
        }
        public Invitation CrtInvitation()
        {
            MyInvitation = new Invitation();
            return MyInvitation;
        }
        //*****************************
        // Create new List Objects
        //*****************************


        public List<TransactionHdr> CrtTransHdrList()
        {
            MyTransHdrList = new List<TransactionHdr>();
            return MyTransHdrList;
        }

        public List<TransactionSeq> CrtTransSeqList()
        {
            MyTransSeqList = new List<TransactionSeq>();
            return MyTransSeqList;
        }

        public List<Environments> CrtEnvironmentList()
        {
            MyEnvironmentList = new List<Environments>();
            return MyEnvironmentList;
        }

        public List<ErrorMsgs> CrtErrMsgsList()
        {
            MyErrMsgsList = new List<ErrorMsgs>();
            return MyErrMsgsList;
        }

        public List<PromptMsgs> CrtPromptMsgsList()
        {
            MyPromptMsgsList = new List<PromptMsgs>();
            return MyPromptMsgsList;
        }

        public List<ConnectionStrings> CrtConnStringList()
        {
            MyConnStringList = new List<ConnectionStrings>();
            return MyConnStringList;
        }

        public List<DisplaySeq> CrtDisplaySeqList()
        {
            MyDisplaySeqList = new List<DisplaySeq>();
            return MyDisplaySeqList;
        }

        public List<TransactionsWaitingToLoad> CrtTransWaitingList()
        {
            MyTransWaitingList = new List<TransactionsWaitingToLoad>();
            return MyTransWaitingList;
        }
        public List<ErrorsEncountered> CrtErrorsEncounteredList()
        {
            MyErrorsEncounteredList = new List<ErrorsEncountered>();
            return MyErrorsEncounteredList;
        }

        public List<InputValues> CrtInpFldValList()         
        {
            MyInpFldValList = new List<InputValues>();
            return MyInpFldValList;
        }


        //*****************************
        // Create new Dictionary Objects
        //*****************************

        public Dictionary<string, int> CrtTagsDict()
        {
            MyTagsDict = new Dictionary<string, int>();
            return MyTagsDict;
        }

        public Dictionary<string, int> CrtCharDict()
        {
            MyCharDict = new Dictionary<string, int>();
            return MyCharDict;
        }

        public Dictionary<string, string> CrtVarValuesDict()
        {
            MyVarValuesDict = new Dictionary<string, string>();
            return MyVarValuesDict;
        }

        public Dictionary<string, string> CrtVarTypesDict()
        {
            MyVarTypesDict = new Dictionary<string, string>();
            return MyVarTypesDict;
        }

        public Dictionary<string, string> CrtCondLvlsDict()
        {
            MyCondLvlsDict = new Dictionary<string, string>();
            return MyCondLvlsDict;
        }

        public Dictionary<string, string> CrtRuleTypeOpSeqDict()
        {
            MyRuleTypeOpSeqDict = new Dictionary<string, string>();
            return MyRuleTypeOpSeqDict;
        }

        public Dictionary<string, string> CrtLogMessagesDict()
        {
            MyLogMessagesDict = new Dictionary<string, string>();
            return MyLogMessagesDict;
        }
        
        public Dictionary<string, string> CrtKeywordsDict()
        {
            MyKeywordsDict = new Dictionary<string, string>();
            return MyKeywordsDict;
        }
        
        public Dictionary<string, int[]> CrtPrevSelDict()
        {
            MyPrevSelDict = new Dictionary<string, int[]>();
            return MyPrevSelDict;
        }

        public Dictionary<string, object> CrtObjDict()
        {
            MyObjDict = new Dictionary<string, Object>();
            return MyObjDict;
        }
        public Dictionary<string, string> CrtAppDistLstDict()
        {
            MyAppDistLstDict = new Dictionary<string, string>();
            return MyAppDistLstDict;
        }
        public Dictionary<string, string> CrtNextNumbersDict()
        {
            MyNextNumbersDict = new Dictionary<string, string>();
            return MyNextNumbersDict;
        }

Open in new window


I am open to any advice you can give me for V2.

Point 2 - Completing the Project

Fernando, the UI logic references all of the above global variables. As you can see there are dictionaries, Lists, Classes etc.. (g.MyVarValuesDict, g.MyTransSeqList, g.MyUser  etc..) What is the best way of copying these objects by value ?

Are you suggesting that I create a globalsB class (identical to globals) which would hold all of the copied (by value) values in globals and then pass globalsB into the background worker as a parameter. Will I still be able to refer to these globalB values as  g.MyVarValuesDict, g.MyTransSeqList, g.MyUser  etc.. ? Would you have a simple example of doing this ?

regards
Pat
>>Without using globals I believe that I would have to copy each of the classes and variables to each .cs file.

Yes, but there is nothing wrong with that.  In fact it has big advantages, here are a couple.
  • It can simplify debugging, it is much easier to track down what is changing a value of one of the variables
  • It makes multi threading much simpler to code - see this question

In general keep variables as close to where you need them as possible and don't let other parts of your code access them if there is no reason for them to need accessing.  In fact just creating one big class to hold all the previously global variables might not be a good idea.  eg. Put database things together and just let those bits of your app requiring database access use them and not other bits that for instance perform custom drawing of a window.
Thanks Andy,
did you forget to add a link  "see this question" ?
Hi pclarke7;

Please see this article from Wikipedia on the good and bad of Global variables some of which reposted below.
They are usually considered bad practice precisely because of their non-locality: a global variable can potentially be modified from anywhere (unless they reside in protected memory or are otherwise rendered read-only), and any part of the program may depend on it.[1] A global variable therefore has an unlimited potential for creating mutual dependencies, and adding mutual dependencies increases complexity. See action at a distance. Global variables also make it difficult to integrate modules because software written by others may use the same global names unless names are reserved by agreement, or by naming convention. However, in a few cases, global variables can be suitable for use. For example, they can be used to avoid having to pass frequently-used variables continuously throughout several functions. In practice, large programs may well require a large number of global variables because there are so many parameters that are shared between different functions, and care must be taken to make sure different functions share the global data without mishap.


Global variables are used extensively to pass information between sections of code that do not share a caller/callee relation like concurrent threads and signal handlers. Languages (including C) where each file defines an implicit namespace eliminate most of the problems seen with languages with a global namespace though some problems may persist without proper encapsulation. Without proper locking (such as with a mutex), code using global variables will not be thread-safe except for read only values in protected memory.
>>did you forget to add a link  "see this question" ?
No, this question as in this question you have asked here and I am commenting about.  It is an example of making life difficult for yourself by using global variables.
Hi Fernando,
I am trying to use your suggested work around but still finding that the global variables in the background thread are being updated by the UI.

1.Create a class that has the variables that are in the global namespace that will be used in the BackgroundWorker
2.Create and instance of that class and copy over the variables from global namespace if a variable is a ref type you will need to create a new object for that variable and not copy it


Below I create a new globalsb  which is identical to globals and will hold the global variables for the background job. Then I clone everything in globals g to globalsb gb.

        public void StartBackgroundProcess()
        {
            try
            {
                if (MyGlobals.logger == null)
                {
                    MyGlobals.logger = new LoggerConfiguration()
                   .ReadFrom.AppSettings()
                   .CreateLogger();
                    Log.Logger = MyGlobals.logger;
                }
                MyGlobals.logger.Information("STARTING BGJ: { BackgroundCancelError} User: {UserName} Enterprise: {Enterprise} Source: {Source} Transaction:{Transaction} Seq:{Seq} CatchNo: {CatchNo}  ErrMsgId: {ErrMsgId} Error Description:{ErrDescr} Error Data:{ErrData} ", "STARTING>>>", g.MyExe.userId, g.MyExe.enterpriseId, g.MyExe.thisSourceMethod, g.MyExe.thisTransId, g.MyExe.curSeq, g.MyExe.catchNo, g.MyExe.errorMsg, g.MyExe.errorDescription, " ");
            }

            catch (Exception ex)
            {
                MyGlobals.logger.Error("STARTING BGJ2: { BackgroundCancelError}", ex.Message.ToString());
            }
            bg_Worker = new BackgroundWorker();
            bg_Worker.DoWork += new DoWorkEventHandler(bg_Worker_DoWork);
            bg_Worker.ProgressChanged += new ProgressChangedEventHandler
                    (bg_Worker_ProgressChanged);
            bg_Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_Worker_RunWorkerCompleted);
            bg_Worker.WorkerReportsProgress = true;
            bg_Worker.WorkerSupportsCancellation = true;

            globalsB gb = new globalsB(); // Background globalsb (identical to globals)
            for (int i = 1; i <= 50; i++)
            {

                switch (i)
                {
                    case 1:       // TransactionService
                        gb.MyTransactionService = (TransactionService)g.MyTransactionService.Clone();
                        break;
                    case 2:       // User
                        gb.MyUser = (UserMaster)g.MyUser.Clone();
                        break;
                    case 3:       // MyRegDetails 
                        gb.MyRegDetails= (RegDetails) g.MyRegDetails.Clone();
                        break;
                    case 4:       // MyEnterprise
                        gb.MyEnterprise= (EnterpriseMaster) g.MyEnterprise.Clone();
                        break;
                    case 5:       // MyDataBaseIO
                        gb.MyDataBaseIO=(DataBaseIO) g.MyDataBaseIO.Clone();
                        break;
                    case 6:       // MyExe
                        gb.MyExe=(ExecuteTrans) g.MyExe.Clone();
                        break;
                    case 7:       // MyErrMsgs
                        gb.MyErrMsgs = (ErrorMsgs) g.MyErrMsgs.Clone();
                        break;
                    case 8:       // MyPromptMsgs
                        gb.MyPromptMsgs=(PromptMsgs)g.MyPromptMsgs.Clone();
                        break;
                    case 9:       // MyConnString
                        gb.MyConnString= (ConnectionStrings) g.MyConnString.Clone();
                        break;
                    case 10:       // MyLogMessages
                        gb.MyLogMessages=(LogMessages) g.MyLogMessages.Clone();
                        break;
                    case 11:       // MyRuleTypeOpSeq
                        gb.MyRuleTypeOpSeq=(RuleTypeOpSeq) g.MyRuleTypeOpSeq.Clone();
                        break;
                    case 12:       // MyErrMsgsList
                        gb.MyErrMsgsList= g.MyErrMsgsList.CloneObject();
                        break;
                    case 13:       // MyEnvironments
                        gb.MyEnvironments=(Environments) g.MyEnvironments.Clone();
                        break;
                    case 14:       // MyPromptMsgsList
                        gb.MyPromptMsgsList=g.MyPromptMsgsList.CloneObject();
                        break;
                    case 15:       // MyConnStringList
                        gb.MyConnStringList=g.MyConnStringList.CloneObject();
                        break;
                    case 16:       // MyEnvironmentList
                        gb.MyEnvironmentList=g.MyEnvironmentList.CloneObject();
                        break;
                    case 17:       // MyLogMessagesDict
                        gb.MyLogMessagesDict=g.MyLogMessagesDict.CloneObject();
                        break;
                    case 18:       // MyRuleTypeOpSeqDict
                        gb.MyRuleTypeOpSeqDict=g.MyRuleTypeOpSeqDict.CloneObject();
                        break;
                    case 19:       // MyKeywordsDict
                        gb.MyKeywordsDict=g.MyKeywordsDict.CloneObject();
                        break;
                    case 20:       // MyCommands
                        gb.MyCommands=(Commands) g.MyCommands.Clone();
                        break;
                    case 21:       // MyObjDict
                        gb.MyObjDict=g.MyObjDict.CloneObject();
                        break;
                    case 22:       // MyNextNumbers
                        gb.MyNextNumbers=(NextNumbers) g.MyNextNumbers.Clone();
                        break;
                    case 23:       // MyAppDistLstDict
                        gb.MyAppDistLstDict=g.MyAppDistLstDict.CloneObject();
                        break;
                    case 24:       // MyNextNumbersDict
                        gb.MyNextNumbersDict=g.MyNextNumbersDict.CloneObject();
                        break;
                    case 25:       // MyInvitation
                        gb.MyInvitation=(Invitation) g.MyInvitation.Clone();
                        break;
                    case 26:       // MyBGWorker
                        gb.MyBGWorker=(BGWorker) g.MyBGWorker.Clone();
                        break;
                    case 27:       // MyTransHdr
                        gb.MyTransHdr=(TransactionHdr) g.MyTransHdr.Clone();
                        break;
                    case 28:       // MyTransSeq
                        gb.MyTransSeq=(TransactionSeq) g.MyTransSeq.Clone();
                        break;
                    case 29:       // MyTransWaiting
                        gb.MyTransWaiting= (TransactionsWaitingToLoad) g.MyTransWaiting.Clone();
                        break;
                    case 30:       // MyErrorsEncountered
                        gb.MyErrorsEncountered=(ErrorsEncountered) g.MyErrorsEncountered.Clone();
                        break;
                    case 31:       // MyDisplaySeq
                        gb.MyDisplaySeq=(DisplaySeq) g.MyDisplaySeq.Clone();
                        break;
                    case 32:       // MyPassToServiceParms
                        gb.MyPassToServiceParms=(PassToServiceParms) g.MyPassToServiceParms.Clone();
                        break;
                    case 33:       // MyRtnFromServiceParms
                        gb.MyRtnFromServiceParms=(RtnFromServiceParms) g.MyRtnFromServiceParms.Clone();
                        break;
                    case 34:       // MyInpFldVal
                        gb.MyInpFldVal=(InputValues) g.MyInpFldVal.Clone();
                        break;
                    case 35:       // MyVarObj
                        gb.MyVarObj=g.MyVarObj.CloneObject();
                        break;
                    case 36:       // MyTransOutput
                        gb.MyTransOutput=(TransOutput) g.MyTransOutput.Clone();
                        break;
                    case 37:       // MyTransHdrList
                        gb.MyTransHdrList=g.MyTransHdrList.CloneObject();
                        break;
                    case 38:       // MyTransSeqList
                        gb.MyTransSeqList=g.MyTransSeqList.CloneObject();
                        break;
                    case 39:       // MyDisplaySeqList
                        gb.MyDisplaySeqList=g.MyDisplaySeqList.CloneObject();
                        break;
                    case 40:       // MyTransWaitingList
                        gb.MyTransWaitingList= g.MyTransWaitingList.CloneObject();
                        break;
                    case 41:       // MyErrorsEncounteredList
                        gb.MyErrorsEncounteredList= g.MyErrorsEncounteredList.CloneObject();
                        break;
                    case 42:       // MyInpFldValList
                        gb.MyInpFldValList=g.MyInpFldValList.CloneObject();
                        break;
                    case 43:       // MyTagsDict
                        gb.MyTagsDict=g.MyTagsDict.CloneObject();
                        break;
                    case 44:       // MyVarValuesDict
                        gb.MyVarValuesDict=g.MyVarValuesDict.CloneObject();
                        break;
                    case 45:       // MyVarTypesDict
                        gb.MyVarTypesDict=g.MyVarTypesDict.CloneObject();
                        break;
                    case 46:       // MyCondLvlsDict
                        gb.MyCondLvlsDict=g.MyCondLvlsDict.CloneObject();
                        break;
                    case 47:       // MyPrevSelDict
                        gb.MyPrevSelDict=g.MyPrevSelDict.CloneObject();
                        break;
                    case 48:       // Constant
                        gb.MyCharDict=g.MyCharDict.CloneObject();
                        break;
                    case 49:       // MyTransLvlValues
                        gb.MyTransLvlValues=(TransLvlValues) g.MyTransLvlValues.Clone();
                        break;
                    case 50:       // MySessionLvlValues
                        gb.MySessionLvlValues=(SessionLvlValues) g.MySessionLvlValues.Clone();
                        break;
                    default:
                        break;
                }
            }

            //
            // Start Background job
            //
            bg_Worker.RunWorkerAsync(gb);  // Call backgroundWorker with list of jobs to run
        }

Open in new window


3.When you call the BackgroundWorker pass in this object as a parameter


I then pass in gb as a parameter to the backgroundWorker:

   bg_Worker.RunWorkerAsync(gb);  // Call backgroundWorker with list of jobs to run

Open in new window

4.In the BackgroundWorker use the values from the object you just passed in.


In  DoWork I do the following:
1.  receive in backgroundWorker global variables gb
2.  Create new global variables g
3.  copy gb to g


       void bg_Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            if(Thread.CurrentThread.Name==null)
            {
                Thread.CurrentThread.Name = "TiosBackgroundWorkerThread";
            }

            //
            // retrieve the passed parameter globalsB and convert to globals
            //
            globalsB gb = e.Argument as globalsB;

            globals g = new globals();    // Globals
            // Create new global variables
            g.MyTransactionService = g.CrtTransactionService();
            g.MyUser = g.CrtUserMaster();
            g.MyRegDetails = g.CrtRegDetails();
            g.MyEnterprise = g.CrtEnterpriseMaster();
            g.MyDataBaseIO = g.CrtDataBaseIO();
            g.MyExe = g.CrtExecuteTrans();
            g.MyErrMsgs = g.CrtErrorMsgs();
            g.MyPromptMsgs = g.CrtPromptMsgs();
            g.MyConnString = g.CrtConnectionStrings();
            g.MyLogMessages = g.CrtLogMessages();
            g.MyRuleTypeOpSeq = g.CrtRuleTypeOpSeq();
            g.MyErrMsgsList = g.CrtErrMsgsList();
            g.MyEnvironments = g.CrtEnvironments();
            g.MyPromptMsgsList = g.CrtPromptMsgsList();
            g.MyConnStringList = g.CrtConnStringList();
            g.MyEnvironmentList = g.CrtEnvironmentList();
            g.MyLogMessagesDict = g.CrtLogMessagesDict();
            g.MyRuleTypeOpSeqDict = g.CrtRuleTypeOpSeqDict();
            g.MyKeywordsDict = g.CrtKeywordsDict();
            g.MyCommands = g.CrtCommands();
            g.MyObjDict = g.CrtObjDict();
            g.MyNextNumbers = g.CrtNextNumbers();
            g.MyAppDistLstDict = g.CrtAppDistLstDict();
            g.MyNextNumbersDict = g.CrtNextNumbersDict();
            g.MyInvitation = g.CrtInvitation();
            g.MyBGWorker = g.CrtBGWorker();
            g.MyTransHdr = g.CrtTransactionHdr();
            g.MyTransSeq = g.CrtTransactionSeq();
            g.MyTransWaiting = g.CrtTransactionsWaitingToLoad();
            g.MyErrorsEncountered = g.CrtErrorsEncountered();
            g.MyDisplaySeq = g.CrtDisplaySeq();
            g.MyPassToServiceParms = g.CrtPassToServiceParms();
            g.MyRtnFromServiceParms = g.CrtRtnFromServiceParms();
            g.MyInpFldVal = g.CrtInputValues();
            g.MyVarObj = g.CrtVarObj();
            g.MyTransOutput = g.CrtTransOutput();
            g.MyTransHdrList = g.CrtTransHdrList();
            g.MyTransSeqList = g.CrtTransSeqList();
            g.MyDisplaySeqList = g.CrtDisplaySeqList();
            g.MyTransWaitingList = g.CrtTransWaitingList();
            g.MyErrorsEncounteredList = g.CrtErrorsEncounteredList();
            g.MyInpFldValList = g.CrtInpFldValList();
            g.MyTagsDict = g.CrtTagsDict();
            g.MyVarValuesDict = g.CrtVarValuesDict();
            g.MyVarTypesDict = g.CrtVarTypesDict();
            g.MyCondLvlsDict = g.CrtCondLvlsDict();
            g.MyPrevSelDict = g.CrtPrevSelDict();
            g.MyCharDict = g.CrtCharDict();
            g.MyTransLvlValues = g.CrtTransLvlValues();
            g.MySessionLvlValues = g.CrtSessionLvlValues();

            // Copy gb to g
            g.MyTransactionService = gb.MyTransactionService;
            g.MyUser = gb.MyUser;
            g.MyRegDetails = gb.MyRegDetails;
            g.MyEnterprise = gb.MyEnterprise;
            g.MyDataBaseIO = gb.MyDataBaseIO;
            g.MyExe = gb.MyExe;
            g.MyErrMsgs = gb.MyErrMsgs;
            g.MyPromptMsgs = gb.MyPromptMsgs;
            g.MyConnString = gb.MyConnString;
            g.MyLogMessages = gb.MyLogMessages;
            g.MyRuleTypeOpSeq = gb.MyRuleTypeOpSeq;
            g.MyErrMsgsList = gb.MyErrMsgsList;
            g.MyEnvironments = gb.MyEnvironments;
            g.MyPromptMsgsList = gb.MyPromptMsgsList;
            g.MyConnStringList = gb.MyConnStringList;
            g.MyEnvironmentList = gb.MyEnvironmentList;
            g.MyLogMessagesDict = gb.MyLogMessagesDict;
            g.MyRuleTypeOpSeqDict = gb.MyRuleTypeOpSeqDict;
            g.MyKeywordsDict = gb.MyKeywordsDict;
            g.MyCommands = gb.MyCommands;
            g.MyObjDict = gb.MyObjDict;
            g.MyNextNumbers = gb.MyNextNumbers;
            g.MyAppDistLstDict = gb.MyAppDistLstDict;
            g.MyNextNumbersDict = gb.MyNextNumbersDict;
            g.MyInvitation = gb.MyInvitation;
            g.MyBGWorker = gb.MyBGWorker;
            g.MyTransHdr = gb.MyTransHdr;
            g.MyTransSeq = gb.MyTransSeq;
            g.MyTransWaiting = gb.MyTransWaiting;
            g.MyErrorsEncountered = gb.MyErrorsEncountered;
            g.MyDisplaySeq = gb.MyDisplaySeq;
            g.MyPassToServiceParms = gb.MyPassToServiceParms;
            g.MyRtnFromServiceParms = gb.MyRtnFromServiceParms;
            g.MyInpFldVal = gb.MyInpFldVal;
            g.MyVarObj = gb.MyVarObj;
            g.MyTransOutput = gb.MyTransOutput;
            g.MyTransHdrList = gb.MyTransHdrList;
            g.MyTransSeqList = gb.MyTransSeqList;
            g.MyDisplaySeqList = gb.MyDisplaySeqList;
            g.MyTransWaitingList = gb.MyTransWaitingList;
            g.MyErrorsEncounteredList = gb.MyErrorsEncounteredList;
            g.MyInpFldValList = gb.MyInpFldValList;
            g.MyTagsDict = gb.MyTagsDict;
            g.MyVarValuesDict = gb.MyVarValuesDict;
            g.MyVarTypesDict = gb.MyVarTypesDict;
            g.MyCondLvlsDict = gb.MyCondLvlsDict;
            g.MyPrevSelDict = gb.MyPrevSelDict;
            g.MyCharDict = gb.MyCharDict;
            g.MyTransLvlValues = gb.MyTransLvlValues;
            g.MySessionLvlValues = gb.MySessionLvlValues;

            // Receive background Global variables into various .cs file   
            g.MyDataBaseIO.ReceiveMyGlob(g);
            g.MyExe.ReceiveMyGlob(g);
            g.MyCommands.ReceiveMyGlob(g);
            g.MyTransactionService.ReceiveMyGlob(g);


            //
            // Read through the transaction list in sequence but only process those transaction sequences which have a keyword
            // of run-AsBGJ (Run as backgrouynd job)
            //
            for (int idx = 0; idx < g.MyTransSeqList.Count; idx++) // Loop through List with for loop
            {
                g.MyTransSeq = g.MyTransSeqList[idx];
                if (g.MyTransSeq.keyword.ToLower().Contains("run-asbgj"))
                {
                    //
                    // process transaction seq if it contains "run-asbgj"
                    //

                    g.MyExe.curSeq = idx;
                    BGWTransId = g.MyTransSeq.thisTransId;
                    g.MyExe.promptId = g.MyTransSeq.promptMsg;
                    MyGlobals.logger.Information("Transaction: {Trans} - Seq: {Seq} SeqListCount: {cnt1} Iteration: {iter} Thread:{thread}", g.MyTransSeq.thisTransId, g.MyTransSeq.seqNo, g.MyTransSeqList.Count().ToString(), idx.ToString(), "BackgroundThread XX-BEFORE-XX");
                    g.MyExe = g.MyTransactionService.ExecuteTransaction2(g.MyTransSeq, idx);
                    MyGlobals.logger.Information("Transaction: {Trans} - Seq: {Seq} SeqListCount: {cnt1} Iteration: {iter} Thread:{thread}", g.MyTransSeq.thisTransId, g.MyTransSeq.seqNo, g.MyTransSeqList.Count().ToString(), idx.ToString(), "BackgroundThread XX -AFTER-XX");

                    //
                    // This is where the seq is overridden by conditions and tags
                    //
                    if (g.MyExe.nextBackgroundSeq != -99)
                    {
                        idx = g.MyExe.nextBackgroundSeq;
                        g.MyExe.nextBackgroundSeq = -99;
                    }
                }
            }

Open in new window


 However my UI is still updating the background worker global variables. Appreciate if you can cast you eye over what I am doing to see if you can spot where I am going wrong

regards
Pat
Hi Pat;

The reason why your modified code did not work was that you copied reference types to the new globals object. Reference types are an address to memory and not an actual value so copying it only copies the address which means that both the old and the new object are pointing to the same old object and not what you needed, two independent objects.

1  : Create a class that has the variables that are in the global namespace that will be used in the BackgroundWorker
In your case you have a class which is globals so you don't have to crate a new class you already have globals all you would need to do is mark the class Serializable shown here
[Serializable()]
public class Globals

Open in new window

All properties/variables in this class which themselves are classes, that class must also be marked Serializable as shown above.

2  : Create and instance of that class and copy over the variables from global namespace if a variable is a ref type you will need to create a new object for that variable and not copy it
This part is not so simple because your class does not implement a Deepcopy which is need to copy over basic variable types as well as create NEW reference types for the reference variables. The below function will do a DeepCopy as long as all the class objects in the globals is not a sealed class and the class has been marked Serializable
using System.IO;                                     
using System.Runtime.Serialization.Formatters.Binary;


private Globals DeepCopyGlobas(globals original)
{
    //Opens a memory stream and serializes the object into it in binary format.
    Stream stream = new MemoryStream();
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(stream, original);

    // Reset the stream to the begining and deserializes the object
    stream.Position = 0;
    formatter = new BinaryFormatter();
    // Rehydrate the global variables into a new object
    globals newGlobals = (Globals)formatter.Deserialize(stream);
    stream.Close();

    return newGlobals;
}

Open in new window


3  : When you call the BackgroundWorker pass in this object as a parameter
bg_Worker.RunWorkerAsync(gb);
// Where gb was the return value from DeepCopy

Open in new window


4  : In the BackgroundWorker use the values from the object you just passed in.
void bg_Worker_DoWork(object sender, DoWorkEventArgs e)
{
	if(Thread.CurrentThread.Name==null)
	{
	    Thread.CurrentThread.Name = "TiosBackgroundWorkerThread";
	}
	
	//
	// retrieve the passed parameter globals. Globals were already copied before entering BGW
	//
	globals g = e.Argument as globals;
	
	
	// Receive background Global variables into various .cs file   
	g.MyDataBaseIO.ReceiveMyGlob(g);
	g.MyExe.ReceiveMyGlob(g);
	g.MyCommands.ReceiveMyGlob(g);
	g.MyTransactionService.ReceiveMyGlob(g);
	
	
	//
	// Read through the transaction list in sequence but only process those transaction sequences which have a keyword
	// of run-AsBGJ (Run as backgrouynd job)
	//
	for (int idx = 0; idx < g.MyTransSeqList.Count; idx++) // Loop through List with for loop
	{
	    g.MyTransSeq = g.MyTransSeqList[idx];
	    if (g.MyTransSeq.keyword.ToLower().Contains("run-asbgj"))
	    {
	        //
	        // process transaction seq if it contains "run-asbgj"
	        //
	
	        g.MyExe.curSeq = idx;
	        BGWTransId = g.MyTransSeq.thisTransId;
	        g.MyExe.promptId = g.MyTransSeq.promptMsg;
	        MyGlobals.logger.Information("Transaction: {Trans} - Seq: {Seq} SeqListCount: {cnt1} Iteration: {iter} Thread:{thread}", g.MyTransSeq.thisTransId, g.MyTransSeq.seqNo, g.MyTransSeqList.Count().ToString(), idx.ToString(), "BackgroundThread XX-BEFORE-XX");
	        g.MyExe = g.MyTransactionService.ExecuteTransaction2(g.MyTransSeq, idx);
	        MyGlobals.logger.Information("Transaction: {Trans} - Seq: {Seq} SeqListCount: {cnt1} Iteration: {iter} Thread:{thread}", g.MyTransSeq.thisTransId, g.MyTransSeq.seqNo, g.MyTransSeqList.Count().ToString(), idx.ToString(), "BackgroundThread XX -AFTER-XX");
	
	        //
	        // This is where the seq is overridden by conditions and tags
	        //
	        if (g.MyExe.nextBackgroundSeq != -99)
	        {
	            idx = g.MyExe.nextBackgroundSeq;
	            g.MyExe.nextBackgroundSeq = -99;
	        }
	    }
}

Open in new window

I hope that made it a bit clearer.
Hi Fernando,
thanks for your detailed response. I have followed your instructions but still finding that my UI objects are updating my backgroundworker objects. I have performed a deep copy of each class within globals g to  globalsB gb  and even used Object.ReferenceEquals to ensure that false is returned for each object cloned. I then pass in globalsB gb as the parameter to DoWork in backgroundworker.

In DoWork,as soon as I receive the parameter  globalsB gb I re-create each class belonging to globals g and then copy the equivalent gb classes to the g classes. I should now be in a position to call the methods that I would usually call via UI , via the backgroundworker thread.

This is what happens.
scenario 1 - when background worker job is submitted it begins to process transactions correctly as expected. However if, before the background job finishes, I go into another transaction via UI then the background job crashes. On examination on the classes I can see that some of the primary Lists used by the background worker (which should be static for the duration of the thread) has changed and now contain the values of the new UI transaction. It's as though they were looking at the same objects rather than separate objects.

scenario 2 - when background worker job is submitted it begins to process transactions correctly as expected. If I allow the job to complete before I maneuver to another transaction then the background job completes successfully.

Question
Lets say  I have a common function Function1() which is called by UI and also by backgroundworker. This function iterates through a list called g.TransSeqList from start to finish. When running Transaction A  the application does the following:

1. Populates list g.TransSeqList from database from transaction A
2. takes a deep copy of g.TransSeqList to gb.TransSeqList  
3. Calls backgroundworker passing gb.TransSeqList as parameter
4. in backgroundworker gb.TransSeqList is copied to g.TransSeqList (shallow copy)
5. backgroundworker calls function Function1() which will iterate through g.TransSeqList (could take 5/10 minutes to complete)

Whilst 5 above is processing the user has control back of UI and goes to a new transaction Transaction B. Transaction B does not use any backgroundworker but instead processes everything using the UI. It does the following:

1. Populates list g.TransSeqList from database from Transaction B
2.UI calls function Function1() which will iterate through g.TransSeqList (This and 5 above will be running at the same time, one in UI and one in backgroundworker)

I am assuming that this logic is ok and the the value of g.TransSeqList in UI (Transaction B) will not conflict or overwrite the value of g.TransSeqList in backgroundworker. Am I right in this assumption ?

regards
Pat
Hi Pat;

I will try to answer your questions but not knowing the layout of the program structure it is difficult. One thing you need to remember is that when you are executing the BackgroundWorker you should be using globalsB gb and NOT g, when NOT executing BackgroundWorker you should be using g.

You state this,
In DoWork,as soon as I receive the parameter  globalsB gb I re-create each class belonging to globals g and then copy the equivalent gb classes to the g classes. I should now be in a position to call the methods that I would usually call via UI , via the backgroundworker thread.
Once you did a DeepCopy which you are calling globalsB gb you have all the parameters you need for the DoWork function you should NOT be doing what I have underlined above. By doing that you are undoing the DeepCopy.

To your statement
Lets say  I have a common function Function1() which is called by UI and also by backgroundworker. This function iterates through a list called g.TransSeqList from start to finish. When running Transaction A  the application does the following:
When you call it from your BackgroundWorker you should be using the DeepCopy / gb copy and NOT g.
Hi Fernando,
ok - maybe this is where the problem lies.  I am indeed  copying globals g to globalsB gb and then in the backgroundworker copying globalsB.gb back to globals g. I was under the assumption that globals g in background thread would be different to globals g in the UI.

The reason why I was doing this is because the logic that the background worker needs to run is identical to that of the UI logic. I have thousands of lines of code referring to g.xxxxxxx and I did not want to have to change logic

From:

if(g.MyTransSeq.transType=='m')  // If Menu
{
  g.MyExe.transType=g.MyTransSeq.transType
  else
  g.MyExe.transType='t'   // Transaction
}

Open in new window


To:
if(currentMode='UI')   // If currently running in UI

if(g.MyTransSeq.transType=='m' )  // If Menu 
{
  g.MyExe.transType=g.MyTransSeq.transType
  else
  g.MyExe.transType='t'   // Transaction
}

else

//
// Job is running as backgroundworker
//

if(gb.MyTransSeq.transType=='m' )  // If Menu and 
{
  gb.MyExe.transType=gb.MyTransSeq.transType
  else
  gb.MyExe.transType='t'   // Transaction
}

Open in new window

I currently have over 20,000 lines of code based on the existing 50 classes in globals g. To cater for g v gb will require a major rework. Is there any way around this ?

regards
Pat
ASKER CERTIFIED SOLUTION
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks Fernando,
I'll give this a go and let you know how I get on.

regards
Pat
Very good.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi Fernando,
sorry for delay in getting back. I have just serialized each of the classes but when I call DeepCopyGlobal(g)  I get the following error. Type 'System.Data.DataRow' in Assembly 'System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable."}

Tried it for just one Object, g.MyUser and had the same error. Have you come across this before ?

regards
Pat
Hi Pat;

I have stated in a previous post the following,
This part is not so simple because your class does not implement a Deepcopy which is need to copy over basic variable types as well as create NEW reference types for the reference variables. The below function will do a DeepCopy as long as all the class objects in the globals is not a sealed class and the class has been marked Serializable
The DataRow class does NOT implement the serialization of this object but the DataTable is marked as Serializable and the DataRow's are members of the DataTable and therefore you get both. So you will need to change your code to use the DataTable in place of the DataRow in Globals and in code go through the DataTable to get to the DataRow objects.
Hi Pat;

Please followup on this question.

Thanks
Fernando
Apologies for delay in responding as I was on holiday. This solution is now working nicely. Thanks for your help
Not a problem pclarke7, glad to help.