我目前正在为自己的家庭项目工作。 该程序使用winforms以C#编写。
我目前遇到的问题如下:
我的主窗体中有一个名为lvwGames的列表视图 当我在没有调试的情况下运行程序时,它运行正常。 但是,当我开始调试时,我收到一个错误。这与后台工作线程有关。
请允许我发布一些代码来帮助我。
private void MainViewLoad(object sender, EventArgs e)
{
RefreshGamesListView();
}
这里没什么特别的。 我调用RefreshGamesListView()的原因是因为我必须多次刷新。
被调用的方法看起来像这样。
public void RefreshGamesListView()
{
pbRefreshGamesList.Value = 0;
bgwRefreshList.RunWorkerAsync();
}
因此,调用该方法时,将调用后台工作程序并运行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);
}
}
当我在debug中运行代码时,代码在lvwGames.Items.Add(li)上; 它给了我以下错误:
Cross-thread operation not valid: Control 'lvwGames' accessed from a thread other than the thread it was created on.
我完全不知道为什么。 我认为这是特定于代码的。但它也可能意味着我没有完全得到后台工作者,特别是何时正确使用它。
我使用它是因为我从数据库中装载了大量大名单的原因,我想保持响应时,在界面加载列表,并告知用户有多远,使用进度杆
如果缺少任何代码,或者您实际上理解为什么会发生这种情况请解释我为什么在这种情况下导致错误。你不需要为我修复它。我只是想知道为什么会造成这种情况。
感谢您抽出宝贵时间阅读这篇文章。我希望能够尽快继续使用调试器。 :)
答案 0 :(得分:2)
从后台线程访问可视控件时,需要调用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方法允许同步 执行控制方法......
答案 1 :(得分:1)
发生这种情况,就像编译器cliams一样,您将从另一个线程更新UI控件内容。您不能这样做,因为UI控件只能在主线程中更新。
请使用提供的示例代码查看此SO答案:
答案 2 :(得分:1)
这是因为您尝试从后台线程访问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 :(得分:0)
如果在studio中以调试模式运行,后台工作程序无法正常工作。如果您有使用Windows句柄检索邮件的调用,则它们将失败。例如,如果您有一个progressChanged事件处理程序,这会更改文本框中可能失败的文本。
我有这样的场景:一个有后台工作者的表单。如果我只是在没有首先获得对话框的情况下启动工作人员,那么它可以正常工作。如果我显示一个对话框,然后启动后台工作程序,那么它将失败。当我正常运行程序时,它不会失败。它以某种方式破坏了事件和前台窗口之间的链接的调试环境。我已经将我的代码更改为使用invoke,现在所有这些都适用于在发行版中运行和调试时。
这是一个链接,解释了如何使程序线程安全。 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线程中运行的所有代码才能安全。