Question

I'm working on a football simulator and i have 9 matches in backgroung on separate threads. And in the method what's in the heart of each thread, there is an event. And when that even occurs (when a goal is "kicked"), I want to update a label (named goalLabel) on the form with the partial result. I wrote a code...:

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

...where on each match the exact count of the goals will be 6(and the result will be 3 - 3), so with 9 (9 is fix too) background matches, the goalLabel should change text (6*9=)54 times. However it only changes a few times. And here's the event's eventhandler method:

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

And the UpdateGoalLabel method:

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

So I can reach and change the label's text, but I dont why it don't changes 54 times. And that would be the goal, to get notified after every single goal.

Any idea?

Thank you in advance.

Update #1: I'm using VS2010.

Here's the code where I launch the threads:

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

Update #2: Here's where I attach the eventhandlers (this is in the same method, few lines above the previous code block):

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;

And I convert those Team arrays to a Match object, because this class has two Team fields, and has the event and the PlayMatch method which is still the method that contains (only) the first code block.

Was it helpful?

Solution

Let me make sure I am understanding what you are doing. You have started 9 threads that loop 6 times, each time updating a text box and sleeping for 1 second. From the sounds of it they are all updating the same label. You commented that if you push the updates to a list you get them all, my guess is that all 9 threads are updating so fast you can't see it and the last one wins.

I am not sure how you are building your UI but I think this would be perfect for WPF and MVVM(Model-View-ViewModel). I wouldn't use WinForms unless you had a damn good reason, WPF is so much easier to work with.

I would create a few view models:

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

Then in the program class on the UI thread, do something like this:

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

I did mine in the override for OnStartup because in VS2010 when you create a project you would have your starup item inherit from System.Windows.Application.

I have this simple UI for testing:

<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>

This code is pretty crude and thrown together to give a quick demo of a MVVM way of doing this. I was able to get all 9 teams to update every second. I had some filler code to simulate the objects you had:

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

OTHER TIPS

I've had problems with UI refreshes too and have worked with "BackgroundWorker" threading directly instead of just "Thread" class. The BackgroundWorker thread allows you to report progress changed explicitly and call some method outside the thread (ie: the calling UI thread that invoked the secondary thread). So, I've created a custom class derived from the background worker, and also created my own version of the "Match" class you have described

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

Now, from your calling window that has the label, such as started from a button click...

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

Yes, it may be more code, but I am explicitly handling who is called back and reporting what on their proper thread... no BeginInvoke required test / actions. Just an alternative to your issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top