Văn Lộc Văn Lộc - 4 months ago 28
C# Question

Multiple thread not working correctly in C#

I create parallel process and DataTable

dtUser
have two rows, it should create two browser:

Parallel.ForEach(dtUser.AsEnumerable(), items =>
OpenBrowser(items["user"].ToString(), items["pass"].ToString()));

Lapsoft_OneDriver browser;
public void OpenBrowser(string username, string password)
{
browser = new Lapsoft_OneDriver(Browsers.Chrome);
browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);
}


It create two Chrome process but only first process running line code block:

browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);


The second process only initializes new browser and not do anything.

If I change this line:

browser = new Lapsoft_OneDriver(Browsers.Chrome);


to

var browser = new Lapsoft_OneDriver(Browsers.Chrome);


It's working.

But another method continues to use variable
browser
to execute other code.

So, I must declare global variable
Lapsoft_OneDriver browser
out of a function to use in another method use it.

My problem is:

Why using
Lapsoft_OneDriver browser;
it create two Chrome process but only first process active, it will insert to
browser.FindElementById("txtUserName")
two values of variable
username
and second process not do anything?

Updated:

When to change the code, I have any problem.

I will add more code of frmMain_Load:

private void frmMain_Load(object sender, EventArgs e)
{
thread = new LThread();
thread.StartedEvent += new LThread.startDelegate(AllCaseProgram);
numLog = int.Parse(dtSetting.Rows[0]["num_Log"].ToString());
}

int numProcess;
private void AllCaseProgram(object args)
{
try
{
switch (numProcess)
{
case 0:
Parallel.ForEach(dtUser.AsEnumerable(), items => Start(items["user"].ToString(), items["pass"].ToString()));
break;
case 1:
ClickCart();
break;
case 2:
Result();
break;
}
}
catch (Exception ex)
{
if (browser != null)
browser.Cleanup();
numProcess = 0;
AllCaseProgram(null);
}
}


At event of button
StartProgram()_Click
. I start Thread like: thread.Start();

You said: should be add this function to my program.

public static void Start(string user, string pwd)
{
var test = new frmMain();
test.OpenBrowser(user, pwd);
test.ClickCart();
}


My update question is:

Seem function
Start(string user, string pwd)
should be change to function
AllCaseProgram
include all switch case.

And variable
numLog
in frmMain_Load have values = 3. In function
test.ClickCart()
I also use this variable but values auto change to 0.

Have any issues with code? Thanks.

And
LThread
class is:

public class LThread : BackgroundWorker
{
#region Members
public delegate void startDelegate(string ID);
public event startDelegate StartedEvent;
private static int RandNumber(int Low, int High)
{
Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), System.Globalization.NumberStyles.HexNumber));

int rnd = rndNum.Next(Low, High);

return rnd;
}
protected override void OnDoWork(DoWorkEventArgs e)
{

StartedEvent(RandNumber(100,10000).ToString()); //put whatever parameter suits you or nothing
base.OnDoWork(e);
e.Result = e.Argument;
}
BackgroundWorker bwThread;
// Main thread sets this event to stop worker thread:
public Boolean bwIsRun;
int m_time_delay = 10000;
Delegate m_form_method_run;
Delegate m_form_method_stop;
Form m_type_form;
#endregion

#region Functions

public void Start()
{
try
{
bwIsRun = true;
this.RunWorkerAsync();
}
catch { }
}
public void Stop()
{
try
{
bwIsRun = false;
}
catch { }
}
private void StartToListen(object sender, DoWorkEventArgs e)
{
while (true)
{
Thread.Sleep(m_time_delay);
if (bwIsRun == true)
{
m_type_form.Invoke(m_form_method_run);
}
else
{
BackgroundWorker bwAsync = sender as BackgroundWorker;
if (bwAsync.CancellationPending)
{
e.Cancel = true;
return;
}
break;
}
}
}

#endregion
}

Answer

You should encapsulate your state for each test run. That way you'll have a class that has the responsibility the start a browser, execute one or more actions, while keeping all the required state belonging to a single run private for just one instance, while you can have a many instances as you like (if resources permit).

// this is NOT a winform, this is a new and seperate class ...
// don't try to mix this with an WinForm, that will fail
public class BrowserTestRunner
{
     // only this Test instances uses this browser
     Lapsoft_OneDriver browser;

     private void OpenBrowser(string username, string password)
     {
         browser = new Lapsoft_OneDriver(Browsers.Chrome);
         browser.GoToUrl(link);
         browser.FindElementById("txtUserName").SendKeys(username);
         browser.FindElementById("txtpassword").SendKeys(password);
         // you probably want to click on something here
     }

     // some other test
     private void ClickCart() 
     {
         browser.FindElementById("btnCart").Click();
     }

     // add other actions here

     // this starts the test for ONE browser
     public static void Start(string user, string pwd)
     {
         var runner = new BrowserTestRunner();
         runner.OpenBrowser(user, pwd);
         // wait for stuff, check data, prepare the next steps
         // for example
         // runner.ClickCart();
         // other actons here
     }
}

Now you can create as many Test class instances as you like, while each instance of the class manages its own internal state, without interfering with other instances:

 Parallel.ForEach(dtUser.AsEnumerable(), items => 
        BrowserTestRunner.Start(items["user"].ToString(), items["pass"].ToString()));

If you want to start that from your backgroundworker do:

private void AllCaseProgram(object args)
{
    try
    {
        switch (numProcess)
        {
            case 0:
                Parallel.ForEach(
                    dtUser.AsEnumerable(), 
                    items => BrowserTestRunner.Start(items["user"].ToString(), items["pass"].ToString()));
                break;
            case 1:
                ClickCart();
                break;
            case 2:
                Result();
                break;
        }
    }
    catch (Exception ex)
    {
        if (browser != null)
            browser.Cleanup();
        numProcess = 0;
        AllCaseProgram(null);
    }
}

By all means: don't start the main form again. Just separate your WinForm from the code you use to operate the browser. That does mean that you have to move the code that interacts with the browser to the BrowserTestRunner. Don't try in keeping the logic for your selenium stuff in the WinForm class because that is doomed to fail. As you are already experiencing.