简体   繁体   English

C#Winform后台工作人员

[英]C# winform backgroundworker

I am currently working on a home project for myself. 我目前正在为自己做一个家庭项目。 The program is written in C# using winforms. 该程序使用Winforms以C#编写。

The problem I'm currently experiencing is as followed: 我当前遇到的问题如下:

I have a listview in my mainform called lvwGames When I run the program without debugging, it runs fine. 我在名为lvwGames的主窗体中有一个列表视图,当我运行该程序而不进行调试时,它运行良好。 However when I start with a debug, I get an error. 但是,当我开始调试时,出现错误。 This has something to do with the background worker thread. 这与后台工作线程有关。

Allow me to post some code to assist me. 请允许我发布一些代码来帮助我。

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

Nothing special here. 这里没什么特别的。 The reason I am calling RefreshGamesListView() is because I have to refresh on several occasions. 我之所以调用RefreshGamesListView()是因为我不得不多次刷新。

The method being called looks like this. 被调用的方法如下所示。

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

So when the method is called, the background worker is called and runs the dowork method. 因此,在调用该方法时,将调用后台工作程序并运行dowork方法。 This one is quite big. 这个很大。

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

When i run the code in debug, when the code is on lvwGames.Items.Add(li); 当我在调试中运行代码时,代码在lvwGames.Items.Add(li);上。 It gives me the following error: 它给了我以下错误:

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

I have absolutely no clue why. 我完全不知道为什么。 I think it is code specific. 我认为这是特定于代码的。 But it can also mean I don't get the background worker completely, and specifically when to use it properly. 但这也可能意味着我没有完全了解后台工作人员,尤其是何时正确使用它。

The reason I'm using it is because I'm loading a large large list from the database, I want to keep responsiveness in the UI when the list is loaded, and inform the users how far it is, using a progress bar. 我之所以使用它,是因为我正在从数据库中加载一个大列表,我想在加载列表时保持UI的响应性,并使用进度条通知用户它有多远。

If any code is missing, or you actually understand why this is happening PLEASE explain me why in this case its causing the error. 如果缺少任何代码,或者您实际上了解了发生这种情况的原因,请向我解释为什么在这种情况下会导致错误。 You don't need to fix it for me. 您不需要为我修复它。 I just want to know WHY it's caused. 我只想知道原因。

Thanks for taking the time to read this post. 感谢您抽出宝贵的时间阅读这篇文章。 I hope to be able to continue using the debugger soon. 我希望能够尽快继续使用调试器。 :) :)

You need to call Conrol.Invoke when accessing visual controls from background threads. 从后台线程访问可视控件时,需要调用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();
    }
}

From Manipulating Controls from Threads 线程控制

...For example, you might call a method that disables a button or updates a display on a form in response to action taken by a thread. ...例如,您可能调用一种方法,以响应线程执行的操作来禁用按钮或更新表单上的显示。 The .NET Framework provides methods that are safe to call from any thread for invoking methods that interact with controls owned by other threads. .NET Framework提供了可以从任何线程安全调用的方法,以调用与其他线程拥有的控件进行交互的方法。 The Control.Invoke method allows for the synchronous execution of methods on controls... Control.Invoke方法允许在控件上同步执行方法...

This happening cause, just like compiler cliams, you are going to update UI control content from another thread. 发生这种情况的原因,就像编译器的用户体验一样,您将从另一个线程更新UI控件内容。 You can not do that, as UI control can be updated only within main thread. 您不能这样做,因为UI控件只能在主线程中更新。

Please have look on this SO answer with example code provided: 请通过提供的示例代码查看此SO答案:

Invoke from another thread 从另一个线程调用

This is because you're attempting to access a UI control ( lvwGames ) from a background thread. 这是因为您试图从后台线程访问UI控件( lvwGames )。 The way to make it work requires you to marshal the information back to the main UI thread and update the control from there: 使其工作的方法要求您将信息封送回主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);
    }
}

Normally you would check the InvokeRequired property first as mentioned in other answers, but there is really no need if you are always calling it from the background thread. 通常,您将首先检查InvokeRequired属性,如其他答案中所述,但是如果始终从后台线程调用它,则实际上并不需要。 Your DoWork method will always require an invoke call, so you might as well just go ahead and write it like that. 您的DoWork方法将始终需要调用调用,因此您最好继续进行编写。

The background worker is not working properly if you run in debug mode in studio. 如果您在Studio中以调试模式运行,则后台工作程序无法正常工作。 If you have calls that use the windows handle to retrieve messages, then they will fail. 如果您有使用Windows句柄检索消息的呼叫,则它们将失败。 If you for instance have a progressChanged event handler and this changes a text in a textbox that might fail. 例如,如果您有一个progressChanged事件处理程序,这将更改文本框中的文本,这可能会失败。

I had this scenario: A Form that has a background worker. 我有这种情况:具有后台工作人员的窗体。 If I just start the worker without getting a dialog box up first then it works ok. 如果我只是在不首先打开对话框的情况下启动工作程序,那么它就可以正常工作。 If I show a dialog and then start the background worker then it fails. 如果显示对话框,然后启动后台工作程序,则它将失败。 When I run the program normally it does not fail. 当我正常运行程序时,它不会失败。 It is somehow the debug environment that destroys the link between the events and the foreground window. 调试环境以某种方式破坏了事件和前台窗口之间的链接。 I have changed my code to use invoke, and now all works both in when running in release and when I debug. 我已经更改了代码以使用invoke,现在所有版本都可以在发行版中运行和调试时使用。

Here is a link explaining what can be done to make a program thread safe. 这是一个链接,说明如何使程序线程安全。 http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx

I did not do the same as the sample to microsoft. 我没有对Microsoft进行相同的示例。 I made delegates, assigned to the functions I needed to run. 我做了代表,分配了我需要运行的功能。 and called invoke on them. 并调用了它们。

sample pseudo code: 样本伪代码:

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

}

All code that potentially have to be run in the GUI thread of the application must be Invoked to be safe. 为了安全起见,必须调用可能必须在应用程序的GUI线程中运行的所有代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM