我正在使用足球模拟器,我在不同的线程上有背景的9场比赛。在方法中,每个线程的核心都有一个事件。当这种情况发生时(当一个目标被“踢”)时,我想用部分结果更新表单上的标签(名为goalLabel)。我写了一个代码......:
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0) homeGoals++;
else awawyGoals++;
if (goal != null) goal(this); //(goal is an event)
Thread.Sleep(1000);
} //this is the full method
......在每场比赛中,目标的确切数量为6(结果为3 - 3),因此9(9也是固定)背景匹配,goalLabel应该更改文本(6 * 9 =)54次。但是它只会改变几次。 这是事件的事件处理程序方法:
public void GoalEventHandler(Match match)
{
string akt = string.Format("{0} {1} - {2} {3}", match.Opps[0].Name, match.Hgoals, match.Agoals, match.Opps[1].Name);
UpdateGoalLabel(akt);
}
和UpdateGoalLabel方法:
public void UpdateGoalLabel(string update)
{
if (InvokeRequired)
{
MyDel del = new MyDel(UpdateGoalLabel); // yeah, I have a delegate for it: delegate void MyDel(string update);
Invoke(del, update);
}
else
{
lock (this) // if this lock isn't here, it works the same way
{
this.goalLabel.Text = update;
}
}
}
所以我可以触及并更改标签的文字,但我不知道为什么它不会改变54次。这就是目标,在每一个目标之后得到通知。
有什么想法吗?
提前谢谢。
更新#1: 我正在使用VS2010。
这是我启动线程的代码:
List<Thread> allMatches = new List<Thread>();
foreach (Match match in matches)
{
Thread newtmatch = new Thread(match.PlayMatch); //this is the first code block I wrote
allMatches.Add(newtmatch);
newtmatch.Start();
}
更新#2: 这是我附加事件处理程序的地方(这是在相同的方法中,在前面的代码块之上几行):
matches = new List<Match>();
foreach (Team[] opponents in Program.cm.nextMatches)
{
Match vmi = new Match(opponents);
matches.Add(vmi);
vmi.goal += new Match.goalevent(GoalEventHandler);
}
//Program.cm.nextMatches is a List<Team[]> object that contains the pairs of teams for the next matches;
我将这些Team数组转换为Match对象,因为这个类有两个Team字段,并且有事件和PlayMatch方法,它仍然是包含(仅)第一个代码块的方法。
答案 0 :(得分:0)
我也遇到了UI刷新问题,并且直接使用“BackgroundWorker”线程而不仅仅是“Thread”类。 BackgroundWorker线程允许您报告显式更改的进度并在线程外调用一些方法(即:调用辅助线程的调用UI线程)。所以,我已经创建了一个派生自后台工作者的自定义类,并且还创建了我自己描述的“匹配”类的版本
public class Match
{
public Match( string Home, string Away )
{
HomeTeam = Home;
HomeGoals = 0;
AwayTeam = Away;
AwayGoals = 0;
}
// simple properties with PROTECTED setters, yet readable by anyone
public string HomeTeam
{ get; protected set; }
public int HomeGoals
{ get; protected set; }
public string AwayTeam
{ get; protected set; }
public int AwayGoals
{ get; protected set; }
// just place-holder since I don't know rest of your declarations
public EventHandler goal;
public void PlayMatch()
{
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0)
HomeGoals++;
else
AwayGoals++;
// Report to anyone listening that a score was made...
if (goal != null)
goal(this, null);
Thread.Sleep(1000);
}
}
}
// Now, the background worker
public class MatchBGW : BackgroundWorker
{
// each background worker preserves the "Match" instance it is responsible for.
// this so "ReportProgress" can make IT available for getting values.
public Match callingMatch
{ get; protected set; }
// require parameter of the match responsible for handling activity
public MatchBGW(Match m)
{
// preserve the match started by the background worker activity
callingMatch = m;
// tell background worker what method to call
// using lambda expression to cover required delegate parameters
// and just call your function ignoring them.
DoWork += (sender, e) => m.PlayMatch();
// identify we can report progress
WorkerReportsProgress = true;
// Attach to the match. When a goal is scored, notify ME (background worker)
m.goal += GoalScored;
}
// this is called from the Match.
public void GoalScored(object sender, EventArgs e)
{
// Now, tell this background worker to notify whoever called IT
// that something changed. Can be any percent, just something to
// trigger whoever called this background worker, so reported percent is 1
ReportProgress(1);
}
}
现在,从具有标签的调用窗口,例如从按钮单击开始...
private void button1_Click(object sender, RoutedEventArgs e)
{
// create your "Matches" between teams
matches = new List<Match>();
matches.Add(new Match("A", "B"));
matches.Add(new Match("C", "D"));
matches.Add(new Match("E", "F"));
foreach (Match m in matches)
{
// create an instance of background worker and pass in your "match"
MatchBGW bgw = new MatchBGW(m);
// tell the background worker that if it is notified to "
// report progress" to, to pass itself (background worker object)
// to this class's SomeoneScored method (same UI thread as textbox)
bgw.ProgressChanged += SomeoneScored;
// Now, start the background worker and start the next match
bgw.RunWorkerAsync();
}
}
// This is called from the background worker via "ReportProgress"
public void SomeoneScored(object sender, ProgressChangedEventArgs e)
{
// Just ensuring that the background worker IS that of what was customized
if (sender is MatchBGW)
{
// get whatever "match" associated with the background worker
Match m = ((MatchBGW)sender).callingMatch;
// add it's latest score with appropriate home/away team names
this.txtAllGoals.Text +=
string.Format("{0} {1} - {2} {3}\r\n",
m.HomeTeam, m.HomeGoals, m.AwayGoals, m.AwayTeam );
}
}
是的,它可能是更多的代码,但我明确地处理了哪些被回调并在其正确的线程上报告了什么......没有BeginInvoke所需的测试/操作。只是您问题的替代方案。
答案 1 :(得分:0)
让我确保我理解你在做什么。您已经启动了9个循环6次的线程,每次更新文本框并休眠1秒。从它的声音,他们都更新相同的标签。您评论说,如果您将更新推送到列表中,您可以获得所有更新,我的猜测是所有9个线程都在快速更新,您无法看到它并且最后一个获胜。
我不确定您是如何构建UI的,但我认为这对于WPF和MVVM(Model-View-ViewModel)来说是完美的。除非你有充分的理由,否则我不会使用WinForms,WPF更容易使用。
我会创建一些视图模型:
public class MainWindowViewModel : DispatcherObject, INotifyPropertyChanged
{
public MainWindowViewModel()
{
Matches = new ObservableCollection<MatchViewModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MatchViewModel> _matches;
public ObservableCollection<MatchViewModel> Matches
{
get { return _matches; }
set
{
_matches = value;
OnPropertyChanged("Matches");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MatchViewModel : DispatcherObject, INotifyPropertyChanged
{
public MatchViewModel()
{
HomeTeam = new TeamViewModel();
AwayTeam = new TeamViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private TeamViewModel _homeTeam;
public TeamViewModel HomeTeam
{
get { return _homeTeam; }
set
{
_homeTeam = value;
OnPropertyChanged("HomeTeam");
}
}
private TeamViewModel _awayTeam;
public TeamViewModel AwayTeam
{
get { return _awayTeam; }
set
{
_awayTeam = value;
OnPropertyChanged("AwayTeam");
}
}
public void PlayMatch()
{
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0)
OnGoalScored(HomeTeam);
else
OnGoalScored(AwayTeam);
Thread.Sleep(1000);
}
}
private void OnGoalScored(TeamViewModel team)
{
if (!team.Dispatcher.CheckAccess())
{
team.Dispatcher.Invoke((Action<TeamViewModel>)OnGoalScored, team);
}
else
{
team.Score++; // do other stuff here
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class TeamViewModel : DispatcherObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private int _score;
public int Score
{
get { return _score; }
set
{
_score = value;
OnPropertyChanged("Score");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
然后在UI线程的程序类中,执行以下操作:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindowViewModel mainWindow = new MainWindowViewModel();
List<Thread> matchThreads = new List<Thread>();
foreach (Team[] opponents in Program.cm.nextMatches)
{
MatchViewModel match = new MatchViewModel();
match.HomeTeam.Name = opponents[0].Name;
match.AwayTeam.Name = opponents[1].Name;
mainWindow.Matches.Add(match);
Thread matchThread = new Thread(match.PlayMatch);
matchThreads.Add(matchThread);
matchThread.Start();
}
MainWindow = new MainWindow();
MainWindow.DataContext = mainWindow;
MainWindow.Show();
}
我在OnStartup的覆盖中做了我的,因为在创建项目的VS2010中,你的starup项目将继承自System.Windows.Application。
我有这个简单的UI用于测试:
<Window x:Class="TestMatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Matches}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} - {2} {3}">
<Binding Path="HomeTeam.Name"/>
<Binding Path="HomeTeam.Score"/>
<Binding Path="AwayTeam.Name"/>
<Binding Path="AwayTeam.Score"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
这段代码很粗糙,并且可以快速演示MVVM的方式。我能够让所有9支球队每秒更新一次。我有一些填充代码来模拟你拥有的对象:
public partial class Program
{
protected override void OnStartup(StartupEventArgs e)
{
...
}
private class Team
{
public string Name { get; set; }
}
private static class cm
{
static cm()
{
nextMatches =
new Team[][]
{
new[] { new Team { Name = "TeamA" }, new Team { Name = "TeamB" }},
new[] { new Team { Name = "TeamC" }, new Team { Name = "TeamD" }},
new[] { new Team { Name = "TeamE" }, new Team { Name = "TeamF" }},
new[] { new Team { Name = "TeamG" }, new Team { Name = "TeamH" }},
new[] { new Team { Name = "TeamI" }, new Team { Name = "TeamJ" }},
new[] { new Team { Name = "TeamK" }, new Team { Name = "TeamL" }},
new[] { new Team { Name = "TeamM" }, new Team { Name = "TeamN" }},
new[] { new Team { Name = "TeamO" }, new Team { Name = "TeamP" }},
new[] { new Team { Name = "TeamQ" }, new Team { Name = "TeamR" }},
};
}
public static Team[][] nextMatches { get; set; }
}
}