多个线程在C#中无法正常工作

时间:2016-08-04 10:39:08

标签: c# selenium

我创建了并行进程,DataTable dtUser有两行,它应该创建两个浏览器:

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);
}

它创建了两个Chrome进程,但只有第一个进程运行行代码块:

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

第二个过程只初始化新浏览器而不做任何事情。

如果我更改此行:

browser = new Lapsoft_OneDriver(Browsers.Chrome);

var browser = new Lapsoft_OneDriver(Browsers.Chrome);

它在工作。

但是另一种方法继续使用变量browser来执行其他代码。

所以,我必须在一个函数中声明全局变量Lapsoft_OneDriver browser,以便在另一个方法中使用它。

我的问题是:

为什么使用Lapsoft_OneDriver browser;创建两个Chrome进程,但只有第一个进程处于活动状态,它会向browser.FindElementById("txtUserName")插入两个变量值username而第二个进程没有执行任何操作?

更新

何时更改代码,我有任何问题。

我将添加更多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);
    }
}

按钮StartProgram()_Click的事件。我启动Thread就像:thread.Start();

你说:应该把这个功能添加到我的程序中。

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

我的更新问题是:

似乎函数Start(string user, string pwd)应更改为函数AllCaseProgram包括所有切换案例。

frmMain_Load中的变量numLog的值为3.在函数test.ClickCart()中我也使用此变量,但值自动更改为0.

代码有任何问题吗?感谢。

LThread类是:

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
}

2 个答案:

答案 0 :(得分:3)

您应该为每次测试运行封装您的状态。这样你就有了一个负责启动浏览器,执行一个或多个动作的类,同时保持属于单个运行的所有必需状态仅为一个实例私有,而你可以拥有许多实例(如果资源允许)。

// 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
     }
}

现在您可以根据需要创建任意数量的Test类实例,而类的每个实例都可以管理自己的内部状态,而不会干扰其他实例:

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

如果你想从你的背景工作者那里开始:

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);
    }
}

无论如何:不要再次启动主表单。只需将WinForm与用于操作浏览器的代码分开即可。这意味着您必须将与浏览器交互的代码移动到BrowserTestRunner。不要试图在WinForm类中保留你的selenium东西的逻辑,因为那注定要失败。正如您已经体验过的那样。

答案 1 :(得分:1)

你在这里得到的是一种竞争条件。在处理类中的单个字段时,有两个线程无法相处。您的问题您没有足够的空间来存储所需的所有浏览器实例。

基本上,第一个线程进入方法,创建chrome浏览器的实例并将其存储在变量中。然后第二个线程进入函数并执行相同的操作。但它也将实例存储在同一个变量中。现在第一个线程继续并转到链接。但它正在使用的实例已经被第二个线程所取代。等等。这可能发生在相反的线程中,或者在处理更多行之后可能发生重叠。但它肯定会出错。

解决问题的方法是,您注意到通过添加var使变量成为本地变量。这样两个线程都使用不同的变量。

现在你说你需要另一个函数中的变量。问题是:你需要两者吗?你只需要一个吗?你需要一个具体的吗?

如果您只需要一个,只需在函数中添加如下行,即可将变量存储在全局变量中:

this.browser = browser;

所以它总体上看起来像这样:

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

我更改了本地浏览器变量的名称,因此可以更清楚地使用哪个变量。请注意,任何一个创建的浏览器都可以在变量中结束。

如果您需要特定的一个,您必须确定是否有正确的一个并在此之后存储结果。

如果您需要两者,则必须将它们存储在列表中。命名空间System.Collections.Concurrent提供了可以由多个线程同时处理的列表。