【问题标题】:C# winform backgroundworkerC# winform 后台工作者
【发布时间】:2014-02-04 18:53:16
【问题描述】:

我目前正在为自己做一个家庭项目。 该程序是使用 Winforms 用 C# 编写的。

我目前遇到的问题如下:

我的主窗体中有一个名为 lvwGames 的列表视图 当我在没有调试的情况下运行程序时,它运行良好。 但是,当我开始调试时,出现错误。这与后台工作线程有关。

请允许我发布一些代码来帮助我。

    private void MainViewLoad(object sender, EventArgs e)
    {
        RefreshGamesListView();
    }

这里没什么特别的。 我之所以调用 RefreshGamesListView() 是因为我必须多次刷新。

被调用的方法如下所示。

    public void RefreshGamesListView()
    {
        pbRefreshGamesList.Value = 0;
        bgwRefreshList.RunWorkerAsync();
    }

所以当方法被调用时,后台worker被调用并运行dowork方法。 这个挺大的。

    private void BgwRefreshListDoWork(object sender, DoWorkEventArgs e)
    {
        List<Game> games = _mainController.RetrieveAllGames();

        int count = 1;
        foreach (Game game in games)
        {
            string id = game.id.ToString();
            var li = new ListViewItem(id, 0);
            li.SubItems.Add(game.title);
            li.SubItems.Add(game.Genre.name);
            li.SubItems.Add(game.Publisher.name);
            li.SubItems.Add(game.Platform.name);
            li.SubItems.Add(game.CompletionType.name);
            li.SubItems.Add(game.gameNotice);
            lvwGames.Items.Add(li);

            double dIndex = (double)(count);
            double dTotal = (double)games.Count;
            double dProgressPercentage = (dIndex / dTotal);
            int iProgressPercentage = (int)(dProgressPercentage * 100);

            count++;
            bgwRefreshList.ReportProgress(iProgressPercentage);
        }
    }

当我在调试中运行代码时,当代码在 lvwGames.Items.Add(li); 它给了我以下错误:

Cross-thread operation not valid: Control 'lvwGames' accessed from a thread other than the thread it was created on.

我完全不知道为什么。 我认为这是特定于代码的。但这也可能意味着我没有完全得到后台工作人员,特别是何时正确使用它。

我使用它的原因是因为我正在从数据库中加载一个大列表,我想在加载列表时保持 UI 中的响应性,并使用进度通知用户它有多远吧。

如果缺少任何代码,或者您确实理解为什么会发生这种情况,请解释为什么在这种情况下它会导致错误。你不需要为我修好它。我只想知道为什么会这样。

感谢您花时间阅读这篇文章。我希望能够尽快继续使用调试器。 :)

【问题讨论】:

  • 这是因为您正在从后台线程更新 UI,而不是调用 UI 线程来进行自己的更新。查看有关此问题的其他问题以获取解决方案。

标签: c# winforms backgroundworker


【解决方案1】:

从后台线程访问可视控件时需要调用Conrol.Invoke

if (_lvwGames.IsHandleCreated) {

    Action addGameToList = () => {
        string id = game.id.ToString();
        var li = new ListViewItem(id, 0);
        li.SubItems.Add(game.title);
        ....
        _lvwGames.Items.Add(li);
    };

    if (_lvwGames.InvokeRequired) {                        
        _lvwGames.Invoke(addGameToList);
    } else {
        addGameToList();
    }
}

来自Manipulating Controls from Threads

...例如,您可能会调用禁用按钮的方法或 更新表单上的显示以响应线程采取的操作。 .NET Framework 提供了可以安全地从任何 用于调用与其他人拥有的控件交互的方法的线程 线程。 Control.Invoke 方法允许同步 在控件上执行方法...

【讨论】:

  • 非常感谢您的帮助。谢谢你。 :)
【解决方案2】:

这是因为您正试图从后台线程访问 UI 控件 (lvwGames)。使其工作的方法需要您将信息编组回主 UI 线程并从那里更新控件:

private void BgwRefreshListDoWork(object sender, DoWorkEventArgs e)
{
    List<Game> games = _mainController.RetrieveAllGames();

    int count = 1;
    foreach (Game game in games)
    {
        string id = game.id.ToString();
        var li = new ListViewItem(id, 0);
        li.SubItems.Add(game.title);
        li.SubItems.Add(game.Genre.name);
        li.SubItems.Add(game.Publisher.name);
        li.SubItems.Add(game.Platform.name);
        li.SubItems.Add(game.CompletionType.name);
        li.SubItems.Add(game.gameNotice);

        // This is the new line you need:
        lvwGames.Invoke(new MethodInvoker(delegate { lvwGames.Items.Add(item) }));

        double dIndex = (double)(count);
        double dTotal = (double)games.Count;
        double dProgressPercentage = (dIndex / dTotal);
        int iProgressPercentage = (int)(dProgressPercentage * 100);

        count++;
        bgwRefreshList.ReportProgress(iProgressPercentage);
    }
}

通常您会首先检查 InvokeRequired 属性,如其他答案中所述,但如果您总是从后台线程调用它,则确实没有必要。您的DoWork 方法将始终需要调用调用,因此您不妨继续这样编写。

【讨论】:

  • 谢谢。这解决了我的问题,也让我知道为什么会这样。
【解决方案3】:

发生这种情况的原因,就像编译器 cliams 一样,您将从另一个线程更新 UI 控件内容。您不能这样做,因为 UI 控件只能在主线程内更新

请使用提供的示例代码查看此 SO 答案:

Invoke from another thread

【讨论】:

    【解决方案4】:

    如果您在工作室中以调试模式运行,后台工作程序将无法正常工作。如果您有使用 Windows 句柄检索消息的调用,那么它们将失败。例如,如果您有一个 progressChanged 事件处理程序,并且这会更改可能失败的文本框中的文本。

    我有这样的场景:一个有后台工作人员的表单。如果我只是启动工作程序而没有先打开对话框,那么它可以正常工作。如果我显示一个对话框然后启动后台工作程序,那么它会失败。当我正常运行程序时,它不会失败。不知何故,调试环境破坏了事件和前台窗口之间的链接。我已将代码更改为使用调用,现在在发布时运行和调试时都可以正常工作。

    这是一个链接,它解释了可以做些什么来使程序线程安全。 http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx

    我没有对微软的样本做同样的事情。我做了代表,分配给我需要运行的功能。并调用它们。

    示例伪代码:

    class MyClassWithDelegates
    {
        public delegate void ProgressDelegate( int progress );
        public ProgressDelegate myProgress;
        public void MyProgress(int progress)
        {
             myTextbox.Text = ..... ; // this is code that must be run in the GUI thread.
        }
    
        public MyClassWithDelegates()
        {
          myProgress = new ProgressDelegate(MyProgress);    
        }
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Invoke( myProgress, e.ProgressPercentage );
        }
    
    }
    

    所有可能必须在应用程序的 GUI 线程中运行的代码都必须被调用才能安全。

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 1970-01-01
      • 2011-10-30
      • 2011-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多