1

我正在开发一个足球模拟器,我在不同的线程上有 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 方法,该方法仍然是包含(仅)第一个代码块的方法。

4

2 回答 2

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 所需的测试/操作。只是您问题的替代方案。

于 2012-11-26T05:29:23.823 回答
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 中,当您创建一个项目时,您的启动项将从 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; }
     }
}
于 2012-11-26T06:25:34.407 回答