Autor Beitrag
doublecross
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 61
Erhaltene Danke: 10

Windows 7
C#; Visual Studio 2015
BeitragVerfasst: Di 04.10.16 16:25 
Hallo Leute,

ich habe noch ein paar Verständnisprobleme mit der TPL, irgendwie will das gerade nicht in meinen Kopf rein, daher hoffe ich, dass mich mal jemand von euch ans Händchen nehmen und mit bei folgenden Szenario helfen kann:

Nehmen wir an, ich habe folgendes:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
    class DatenVerwaltungsObjekt : DatenbenachtichtigunsObjekt
    {


        public void RefreshData(string Filter = "")
        {
            
        }

        protected DatenObjekt BeschaffeDatenAufUnglaublichLangsameArt(string Filter)
        {
            // Macht furchtbar komplizierte Dinge, die ihre Zeit brauchen
        }

        protected DatenObjekt _daten = new DatenObjekt();

        public DatenObjekt Daten
        {
            get { return _daten; }
            protected set
            {
                _daten = value;
                FeuereNeueDatenSindDaEvent();
            }
        }

    }


Dieses Objekt soll mit Daten beschaffen, welche dann über das Property "Daten" ausgelesen werden kann. Hierbei wird Daten in der Regel nach MVVM Manier an ein Oberflächenelement gebunden sein, muss dies aber nicht zwingend. Da die Beschaffung der Daten relativ lange Dauert und es auch nicht wichtig ist, ob sie sofort oder erst in einer Minute angezeigt werden möchte ich gerne folgendes erreichen.

Beim Aufruf von "RefreshData" soll "BeschaffeDatenAufUnglaublichLangsameArt" in einem eigenen Task ausgeführt werden. Das Programm soll dabei völlig normal weiterlaufen und bedienbar sein. Wenn dann irgendwann die Funktion "BeschaffeDatenAufUnglaublichLangsameArt" fertig ist, soll das Ergebnis auf Daten zugewiesen werden, dieses Feuert das entsprechenden Event, damit die Oberfläche aktualisiert werden kann.

Hierbei stellen sich mir insbesondere zwei Fragen:
  1. Wie muss ich meinen Aufruf gestalten, damit ich nicht auf das beenden des Task warten muss, aber dennoch weiter arbeiten kann wenn er Fertig ist?
  2. Wie bekomme ich das Ergebnis der Funktion aus dem Arbeits-Task wieder Syncron mit dem Aufrufenden Task, so dass ich es auf dessen Daten-Property zuweisen kann?


Ich hoffe das war verständlich (und vereinfacht genug), so dass ihr mir helfen könnt :)
Frühlingsrolle
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 952
Erhaltene Danke: 126

[Win NT] 5.1 x86 6.1 x64
[Delphi] 7 PE, 2006, 10.1 Starter, Lazarus - [C#] VS Exp 2012 - [Android API 15] VS Com 2015, Eclipse, AIDE
BeitragVerfasst: Di 04.10.16 18:02 
Guten Abend doublecross,

mir fällt spontan die Task.ContinueWith - Methode dazu ein. Wie genau man an die Sache herangehen sollte, müsste ich erst überlegen. Das ist garnicht so einfach. Mal schauen, was den anderen so einfallen wird.

_________________
„Nicht für das Leben, sondern für die Schule lernen wir.“ „Kürze die lange Rede, damit sie nicht verdächtig wirke!“
(Lucius Annaeus Seneca : 1 - 65 n. Chr)
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 529
Erhaltene Danke: 51

Windows 10, Kubuntu, Android
C# (Visual Studio 2015), Java (IntelliJ), C /C++ (CLion)
BeitragVerfasst: Di 04.10.16 20:21 
Hallo,

ändere deine Klasse in die folgende Form:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
 class DatenVerwaltungsObjekt : DatenbenachtichtigunsObjekt
    {


        public async Task<DatenObjekt> RefreshDataAsync(string Filter = "")
        {
            // Macht furchtbar komplizierte Dinge, die ihre Zeit brauchen
      // und gibt am Ende einfach die Daten via return zurück
        }
    }


Und in den Commands rufst du dann einfach die Methode auf:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
 class ViewModel
    {


        public async void OnXYZCommandExecuted() // dein Tunnel zum Button Click oder was auch immer
        {
            DatenObjekt daten = await deineDatenVerwaltungsObjektInstanz.RefreshDataAsync();
      Name = daten.Name;
      Wert = daten.Wert;
      Property1 = daten.PropertyXY;
      // usw...
        }
    }


Wenn ich mich nicht irre sollte es das schon sein.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1040
Erhaltene Danke: 123

Windows 10 x64 Home Premium
C# (VS 2015 Enterprise)
BeitragVerfasst: Di 04.10.16 23:31 
Ich denke, mit dem Konzept wirst Du recht schnell noch sehr viel größere Probleme haben ;)
WPF möchte nämlich nicht aus keinem anderen Thread genutzt werden, als dem, in dem es läuft.

Die einfachste Lösung wäre, wenn Du eine async-Methode schreibst:

ausblenden C#-Quelltext
1:
2:
3:
4:
private async Task<DataObject> LoadDataAsync(string filter = "")
{
    // ...
}


und musst dann über Dispatcher.Invoke die Property setzen:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
private async void RefreshData()
{
    var viewModel = DataContext as MyViewModel;

    await viewModel.LoadDataAsync("1234")
        .ContinueWith(task =>
        {
            if (Dispatcher.CheckAccess())
                viewModel.Data = task.Result;
            else
                Dispatcher.Invoke(() => DataContext = task.Result);
        });
}


Das hat aber den Nachteil, dass die RefreshData-Methode im CodeBehind liegen muss oder Du im ViewModel den Dispatcher brauchst.
Beides ist streng betrachtet eher dirty MVVM.

Der Alternativ-Vorschlag passt besser zu MVVM: Die IsAsync-Property vom Binding
Die einfach auf true setzen und die Property wird asynchron abgefragt.
Du könntest in deinem ViewModel dann z.B. eine Filter-Property setzen. Diese Filter-Property setzt intern aber auch die Data-Property auf null.
WPF merkt also, dass die Data-Property neu abgefragt werden muss und startet einen Thread, in dem das getan wird.
In der Data-Property prüfst Du dann, ob das Objekt null ist. Ist es null, dann fragst Du die Daten mit Hilfe des Filters synchron ab, aktualisierst das backing-field und gibst es zurück.

Ist doof, dass die Property dann synchron Daten abfragt, da das eben auch der Fall ist, wenn man die Property nutzt. Allerdings ist dafür das Management im Bezug auf die View bedeutend einfacher.
Wenn die abagerufenen Daten aber zwischengespeichert werden, muss nur einmal abgerufen werden.
Alternativ kannst Du ja auch immer noch eine async-Methode verwenden, die nur die Property asynchron abfragt.


Das wären die zwei einfachsten Möglichkeiten, die mir jetzt so spontan einfallen