2016-09-21 2 views
-3

Ich habe eine Stephen Cleary's article gelesen, dass es erforderlich ist, explizit von TaskScheduler.Current bis TaskScheduler.Default zu setzen.Task.Factory.StartNew mit TaskScheduler.Default friert UI ein. Net 4.0

Wenn mein Programm gestartet wird, beginnt das Laden von Daten. Es dauert ungefähr 5 Sekunden. Wenn ich beim Laden der Daten auf die Schaltfläche Hinzufügen klicke, wird das neue Fenster erst geöffnet, wenn Task der Vorgang ReadData() nicht abgeschlossen ist.

Ansichtsmodell:

public class MainWindowVM:ViewModelBase 
{ 
     public MainWindowVM() 
     { 
     AddPersonCommand = new RelayCommand<object>(AddPerson);      
     ReadData(); 
    } 

    private void ReadData() 
    { 
     PersonData = new ObservableCollection<PersonDepBureau>(); 
     IList<PersonDepBureau> persons;   
     Task.Factory.StartNew(() => 
     { 
      using (PersonDBEntities db = new PersonDBEntities()) 
      { 
       persons = (from pers in db.Person 
         join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau 
         join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep 
         select new PersonDepBureau 
         { 
          IdPerson = pers.IdPerson, 
          PersonName = pers.Name, 
          Surname = pers.Surname, 
         ).ToList(); 
       Application.Current.Dispatcher.BeginInvoke(new Action(() => 
       { 
        foreach (var person in persons) 
        { 
         PersonData.Add(person); 
        } 
       })); 
      } 
     }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);   
    } 

    //Opening new Window AddPersonWindow.xaml 
    private void AddPerson(object obj) 
    { 
     AddPersonWindow addPersonWindow = new AddPersonWindow(); 
     addPersonWindow.DataContext new AddPersonVM();    
     addPersonWindow.ShowDialog(); 
    } 
} 

Ausblick:

<extToolkit:BusyIndicator IsBusy="{Binding IsBusy}" Foreground="Black"> 
    <DataGrid ItemsSource="{Binding PersonData}" SelectedItem="{Binding SelectedPerson}" 
     IsSynchronizedWithCurrentItem="True"> 
    <DataGrid.Columns> 
     <DataGridTextColumn Header="Name" Binding="{Binding PersonName}"/> 
     <DataGridTextColumn Header="Surname" Binding="{Binding Surname}" />   
    </DataGrid.Columns> 
    </DataGrid> 
</extToolkit:BusyIndicator> 
<Grid Grid.Row="1">  
    <Button Command="{Binding AddPersonCommand}" Margin="5" Grid.Column="1">Add</Button> 
</Grid> 

nicht async/await syntaktischen Zucker verwenden können, wie meine Anwendung kann in Windows XP verwendet werden (ich kann nicht Benutzer fragen .NET 4.5 zu installieren). Danke im Voraus.

Es ist wirklich komisches Verhalten. Alle Informationen, die ich gelesen habe, verwenden Task wie ich. Aber mein Beispiel funktioniert nicht richtig (neues Fenster wird beim Laden der Daten nicht geöffnet), um das Fehlerverhalten I've made a test application and it can be downloaded here. anzuzeigen. Weil ich viele Kommentare gehört habe, um TaslScheduler.Default zu verwenden.

Bitte schließen Sie meinen Thread nicht, da es für mich wirklich wichtig ist, den Grund zu verstehen, warum meine Benutzeroberfläche nicht reagiert.

Stephen Cleary Artikel ist perfekt. Vielen Dank für Ihre Geduld an Peter Bons. Die Konstruktion Task.Factory.StartNew(() =>}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); ist perfekt funktioniert, der Grund für das Einfrieren der Benutzeroberfläche ist eine andere Operation auf UI-Thread ausgeführt.

+0

Wenn Sie 'Dispatcher. (Begin) Invoke()' aufrufen, führen Sie den Lambda-Ausdruck im UI-Thread aus. Entfernen Sie den Anruf und rufen Sie ihn nur auf, wenn Sie die Steuerelemente/Elemente der Benutzeroberfläche aktualisieren müssen. –

+0

@VisualVincent Wenn ich Dispatcher lösche, friert es die Benutzeroberfläche immer noch ein. – StepUp

+0

Und wenn Sie 'TaskCreationOptions.AttachedToParent' in' TaskCreationOptions.None' ändern? –

Antwort

3

Einer Ihrer Problem dieses Codes liegt in aufrufen :

Task.Factory.StartNew(() => 
     { 
      using (PersonDBEntities db = new PersonDBEntities()) 
      { 
       try 
       { 
        persons = (from pers in db.Person 
           join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau 
           join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep 
           select new PersonDepBureau 
           { 
            IdPerson = pers.IdPerson, 
            PersonName = pers.Name, 
            Surname = pers.Surname, 
            CurrentBureau = bu, 
            CurrentDepartament = dep 
           }).ToList(); 
        Application.Current.Dispatcher.BeginInvoke(new Action(() => 
        { 
         foreach (var person in persons) 
         { 
          PersonData.Add(person); 
         } 
        })); 

       } 
       catch (Exception ex) 
       { 
        MessageBox.Show(ex.Message); 
       }  
      } 
      IsBusy = false; 
     }, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default); 

Sie versuchen, die Benutzeroberfläche von einem anderen Thread zu aktualisieren. Deshalb verwenden Sie den Dispatcher. Sie sollten das Datenladen von der tatsächlichen (UI) Verarbeitungslogik aufteilen. Da Sie nicht async verwenden können/erwarten Sie ContinueWith

so etwas wie (Pseudo-Code) verwenden müssen:

var loadDataTask = Task.Factory.StartNew(() => 
     { 
      var persons = new List<PersonDepBureau>(); 
      using (PersonDBEntities db = new PersonDBEntities()) 
      { 
        persons = (from pers in db.Person 
           join bu in db.Bureau on pers.Fk_IdBureau equals bu.IdBureau 
           join dep in db.Departament on pers.Fk_IdDep equals dep.IdDep 
           select new PersonDepBureau 
           { 
            IdPerson = pers.IdPerson, 
            PersonName = pers.Name, 
            Surname = pers.Surname, 
            CurrentBureau = bu, 
            CurrentDepartament = dep 
           }).ToList(); 
      } 

      return persons; 
     }, CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default); 
loadDataTask.ContinueWith((t) => 
{ 
    // you can check t.Exception for errors 

    // Do UI logic here. (On UI thread) 
    foreach (var person in t.Result) 
    { 
      PersonData.Add(person); 
    } 
}, TaskScheduler.FromCurrentSynchronizationContext()); 

Siehe auch Task continuation on UI thread

Dann ein weiteres Problem ist die Tatsache, dass, wenn Sie auf die Schaltfläche Hinzufügen drücken Eine Instanz der AddPersonVM wird erstellt. Mit diesem Code im Konstruktor:

public AddPersonVM() 
    { 
     LoadData(); 

     AddPersonCommand = new RelayCommand<object>(AddPerson); 
     CancelPersonCommand = new RelayCommand<object>(CancelPerson); 
    } 

Wenn ich Zeit die Dauer dieser LoadData() nenne es irgendwo zwischen 1,5 und 4s vollständig erfolgt. Dieser Code muss ausgefüllt werden, bevor ShowDialog() aufgerufen wird.

Sie sollten keine Daten in Konstruktoren laden.Und in Ihrem Fall nicht bevor der Dialog angezeigt wird. Sie können diese Daten entweder auch asynchron laden oder Sie öffnen das Dialogfeld und verwenden das Window.Loaded-Ereignis, um die Daten abzurufen (sync oder async). Da zu diesem Zeitpunkt die Benutzeroberfläche bereits angezeigt wird, können Sie beim Abrufen der Daten aus der Datenbank einen Indikator verwenden.

Im Hauptfenster haben Sie diesen Code:

private void AddPerson(object obj) 
    { 
     AddPersonWindow addPersonWindow = new AddPersonWindow(); 
     AddPersonVM addPersonVM = new AddPersonVM(); // This synchronous code takes about 2 to 5 seconds to complete 
     addPersonWindow.DataContext = addPersonVM;    
     addPersonVM.OnRequestClose += (sender, args) => { addPersonWindow.Close(); }; 
     addPersonVM.OnPersonSave += addPersonVM_OnPersonSave; 
     addPersonWindow.ShowDialog(); 
    } 

Da der Konstruktor von AddPersonVM lädt Daten aus der Datenbank um 2 bis 5 Sekunden unter den addPersonWindow abgeschlossen ist direkt nicht gezeigt.

+0

Danke für einen Versuch, aber es friert immer noch UI ein. Es ist wirklich interessant, was der Grund ist. Ich habe getan, was Sie vorgeschlagen haben. Vielleicht hast du andere Vorschläge? Ich bin offen für jeden Vorschlag :). – StepUp

+0

@StepUp Es ist eine Menge Arbeit, Ihnen bei der Erstellung einer reaktionsfähigen Anwendung zu helfen. Ich werde versuchen, Ihnen einen Rat zu geben, siehe aktualisierte Antwort. Sie müssen über Ihre synchronen Methoden und den besten Ort denken, um sie aufzurufen. –

+0

danke für eine Hilfe, aber warum 'Dieser Code muss abgeschlossen werden, bevor ShowDialog() aufgerufen wird.Wenn wir Multithreading verwenden. Warum sollte Thread von 'LoadData()' auf UI-Thread warten? – StepUp

0

Sie Ihre Arbeit auf dem Dispatcher tun .....

Könnte nicht das Problem sein, wie ich Ihre Thread.Sleep außerhalb dass gerade gesehen

+0

Wenn ich den Dispatcher lösche, wird die Benutzeroberfläche immer noch eingefroren. – StepUp