2012-11-26 50 views
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方法,它仍然是包含(僅)第一個代碼塊的方法。

+0

哪個版本的C#與VS2005,VS2010等一起工作?另外,WinForms,WPF?最後,你可以顯示你所擁有的代碼,啓動你的9個線程,開始整個調用過程... – DRapp

+0

我使用的是VS2010,我用代碼更新了「問題」。 哦,它是一個窗體。 – user1852051

+0

你在哪裏附加事件處理程序? – akatakritos

回答

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中繼承Starup項目。

我對測試這個簡單的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; } 
    } 
} 
+0

我讓你確定,你明白我在做什麼。 – user1852051

+0

該死的好理由?我只學過winforms。 :D我正在ATM機上工作,我試圖理解你寫的代碼(強硬的我沒有VS在我面前)。所以你說winforms不能勝任,或者WPF是幹什麼的?你能(或其他人)解釋它爲什麼?我用winforms編寫完整的程序,我用ShowDialog()從另一個winform中調用這個模擬器。我也可以用WPF來做到這一點嗎?在這個比賽模擬器的形式中,我有很多其他的東西,我當然想避免再寫一遍。那麼,誰能解釋一下? – user1852051

+0

啊......我嘗試實現你的代碼,但我不知道如何使用WPF。 – user1852051

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需要什麼樣的測試/動作。只是您的問題的替代方案。

+0

謝謝,但這是大學的一項任務,我需要使用Thread。 (我已經在使用beckgroundworker進行其他任務。) – user1852051