【问题标题】:Background Worker to populate combobox in windows form application后台工作人员在 Windows 窗体应用程序中填充组合框
【发布时间】:2014-10-20 06:17:23
【问题描述】:

我想在我的 Windows 应用程序中实现后台工作程序。 目前我正在使用按钮事件处理程序来加载带有数据的组合框。由于查询挂起用户界面,我想实现后台工作者,因为查询在不同的线程中运行。我从来没有在我的任何应用程序中使用过这个后台工作者。我对此进行了一些研究,但仍然无法实施。任何帮助或建议将不胜感激。

这就是我的按钮事件处理程序的样子

private void button6_Click(object sender, EventArgs e)
        {
            if (comboBox1.SelectedItem.ToString() == "All")
            {

               findAllUser();

            }

            else
            {
                //Do Something!!!

            }
        }     

findAllUser() 将从活动目录中获取所有用户,这通常需要时间并使 UI 无响应。 findAllUser() 的代码如下所示。

public void findAllUser()
    {
        System.DirectoryServices.DirectoryEntry entry = new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
        System.DirectoryServices.DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(entry);
        mySearcher.Filter = "(&(objectClass=user))";

        foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
        {
            try
            {
                System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
                comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
            }

            catch (Exception)
            {
                // MessageBox.Show(e.ToString());
            }
        }

    }

下面是后台工作人员现在的样子..都是空的

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {


        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {


        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

任何建议我如何实现上面的代码,以便后台工作人员使用活动目录用户列表填充组合框2。

【问题讨论】:

  • 网络上有大量的基本示例演示了 Background 的 worker 简单使用。好好看看,让你的代码工作,来吧...dotnetperls.com/backgroundworker

标签: c# winforms active-directory backgroundworker


【解决方案1】:

最简单的方法是让您的findAllUser 方法构建需要添加到组合框中的项目列表,然后让RunWorkerCompleted 方法填充组合框。例如,像这样修改您的findAllUser

private List<string> items;
public void findAllUser()
{
    items = new List<string>();

    System.DirectoryServices.DirectoryEntry entry =
        new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
    System.DirectoryServices.DirectorySearcher mySearcher =
        new System.DirectoryServices.DirectorySearcher(entry);
    mySearcher.Filter = "(&(objectClass=user))";

    foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
    {
        try
        {
            System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
            items.Add(de.Properties["GivenName"].Value.ToString() + " " +
                de.Properties["sn"].Value.ToString() + " " + "[" +
                de.Properties["sAMAccountName"].Value.ToString() + "]");
        }

        catch (Exception)
        {
            // MessageBox.Show(e.ToString());
        }
    }
}

然后,让您的DoWork 致电findAllUser 进行实际工作。

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    findAllUser();
}

最后,让您的 RunWorkerCompleted 填充组合框:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    foreach (var item in items)
    {
        comboBox2.Items.Add(item);
    }
}

如果你想显示进度,那么你需要在工人做生意的时候不时打电话给ReportProgress。由于您不确切知道该过程需要多长时间或确切找到多少用户,因此您无法真正报告准确的进度。在这种情况下,您必须猜测。既然你说它需要“大约 30 秒”,那么你可以将其用作 100% 标记。因此,您在工作人员开始处理时启动StopWatch,并每半秒左右更新一次。像这样修改findAllUser 函数:

public void findAllUser()
{
    const int ExpectedTime = 30000; // 30,000 milliseconds
    // stopwatch keeps track of elapsed time
    Stopwatch sw = Stopwatch.StartNew();
    // Create a timer that reports progress at 500 ms intervals
    System.Timers.Timer UpdateTimer;
    UpdateTimer = new System.Threading.Timer(
        null,
        {
            var percentComplete = (100 * sw.ElapsedMilliseconds) / ExpectedTime;
            if (percentComplete > 100) percentComplete = 100;

            ReportProgress(percentComplete);

            // Update again in 500 ms if not already at max
            if (percentComplete < 100)
                UpdateTimer.Change(500, Timeout.Infinite);
        }, 500, Timeout.Infinite);
    items = new List<string>();

    // rest of findAllUser here

    // dispose of the timer.
    UpdateTimer.Dispose();
}

然后,在您的 ProgressChanged 事件处理程序中,更新您的进度条。

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // Update the progress bar with the value from e.ProgressPercentage

    }

同样,由于您不知道具体需要多长时间,因此您需要进行估算。上面,我估计30秒,代码盲目假设15秒过去了,那么就完成了一半。

请注意,我将计时器创建为一次性,并在每次滴答后重新初始化它。我这样做是因为我想防止并发更新。计时器在单独的线程上触发,ReportProgress 方法将对ProgressChanged 事件的调用编组到 UI 线程。如果 UI 线程忙于其他事情,则可能会出现另一个计时器事件,并且您最终可能会遇到一堆线程都试图编组对 UI 的调用。在这种情况下可能不是问题,因为我们最多只能进行 60 次调用(每秒 2 次调用,持续 30 秒),但总的来说,防止这种事情发生是个好主意。

【讨论】:

  • 是的,我实际上想查看进度条中的进度,因为从域中查找用户通常需要 30 秒,我希望用户通过进度条看到后端正在发生的事情。您能否建议我如何在您的解决方案中实施进度条。谢谢。
【解决方案2】:

将您的代码放在 backgroundWorker1_DoWork 上。但我建议您使用 ThreadTask Parallel Library

如果您使用的是 .NET 4.0,请使用 TPL。

你可以这样做:

    Task runner = new Task(() =>
    {
        // do process here
    });
    runner.Start();

如果您使用的是旧框架,请像这样使用线程。

    Thread thread = new Thread(() =>
    {
        // do process here
    });
    thread.IsBackground = true;
    thread.Start();

详细了解TPLThread

【讨论】:

  • 不要使用线程。如果您打算将它与 async/await 一起使用,请使用 TPL。否则 BackgroundWorker 是一种更简洁的方法,因为它抽象出了跨线程问题。
  • @PeterDuniho 我认为 BackgroundWorker 也遇到了跨线程问题。我发现线程在这种情况下更适用,但话又说回来,异步是异步的。
  • 您认为 BackgroundWorker 遇到了哪些跨线程问题?当然,请注意 BackgroundWorker 的全部目的是让用户避免不得不处理它们。进度通知和完成事件会自动委托给正确的线程,用户无需进行额外工作。原始线程需要调用者自己处理跨线程调度,TPL 也是如此,除非您使用 async/await(正如我已经指出的那样)。
【解决方案3】:

使用 BackgroundWorker 很方便,因为它会自动调用 UI 线程中的 ProgressChanged 和 RunworkerCompleted 事件处理程序。你可以像下面这样使用它。

    private void AddItem(DirectoryEntry de)
    {
        comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
    }

    private void button6_Click(object sender, EventArgs e)
    {
        if (comboBox1.SelectedItem.ToString() == "All")
        {

            this.backgroundWorker1.RunWorkerAsync();

        }

        else
        {
            //Do Something!!!

        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // Bind to the users container.
        DirectoryEntry entry = new DirectoryEntry("LDAP://CN=xyz,DC=com");
        // Create a DirectorySearcher object.
        DirectorySearcher mySearcher = new DirectorySearcher(entry);

        try
        {
            // Create a SearchResultCollection object to hold a collection of SearchResults
            // returned by the FindAll method.
            SearchResultCollection result = mySearcher.FindAll();
            int count = result.Count;

            for(int i = 0; i < count; i++)
            {
                SearchResult resEnt = result[i];

                try
                {
                    DirectoryEntry de = resEnt.GetDirectoryEntry();

                    BeginInvoke(new Action<DirectoryEntry>(AddItem), de);
                }
                catch (Exception)
                {
                    // MessageBox.Show(e.ToString());
                }

                this.backgroundWorker1.ReportProgress(i / count);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.progressBar1.Value = e.ProgressPercentage;
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.progressBar1.Value = 100;
    }

【讨论】:

  • 它不会自动调用 ProgressChanged,正如您从示例中看到的那样,您显式调用它;)
  • 我在 DoWork 事件处理程序中显式调用它,因为这个处理程序是从线程池线程调用的,而不是 UI 线程。 ProgressChanged 事件处理程序由 BackgroundWorker 调用。
  • @YoupTube:我相信 Tu Le 的意思(坦率地说,这是他写的)是“自动”的一点是事件是在正确的线程(GUI 线程)上引发的。而这也是BackgroundWorker的一个重要特性;这几乎是使用它的主要原因(尽管今天使用 async/await,它的吸引力要小得多)。
  • @TuLeHong,您的解决方案引发了未处理的异常。
  • 我认为 DO Work 没有获得任何值,因此它正在将空值传递给 AddItem 方法。
【解决方案4】:

您可以使用以下逻辑为您的代码实现后台工作器。

var startListenerWorker = new BackgroundWorker();
                startListenerWorker.DoWork += new DoWorkEventHandler(this.StartListenerDoWork);
                startListenerWorker.RunWorkerAsync();


private void StartListenerDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
        {

// Your logic to load comboBox will go here for running your query
}

您还可以实现线程,让您的逻辑在单独的线程上运行。

【讨论】:

    猜你喜欢
    • 2014-04-01
    • 1970-01-01
    • 2011-10-07
    • 1970-01-01
    • 2011-11-17
    • 1970-01-01
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    相关资源
    最近更新 更多