Autor Beitrag
floho
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Do 15.09.16 12:10 
Hallo liebe Community,

ich habe eine WPF Anwendung mit MVVM Pattern + PRISM erstellt.

Leider habe ich ein Problem das meine Anwendung durch einen Deadlock einfriert.
Zitat:
Managed Debugging Assistant 'ContextSwitchDeadlock' has detected a problem in 'D:\TFS_Workspace\...\.vshost.exe'.

Additional information: Die CLR konnte 60 Sekunden lang keinen Übergang vom COM-Kontext 0x8da140 zum COM-Kontext 0x8da1f8 durchführen. Der Thread, der Besitzer des Zielkontexts/-apartments ist, wartet entweder, ohne Meldungen zu verschieben, oder verarbeitet eine äußerst lang dauernde Operation, ohne Windows-Meldungen zu verschieben. Eine solche Situation beeinträchtigt in der Regel die Leistung und kann sogar dazu führen, dass die Anwendung nicht mehr reagiert oder die Speicherauslastung immer weiter zunimmt. Zur Vermeidung dieses Problems sollten alle STA-Threads (Singlethread-Apartment) primitive Typen verwenden, die beim Warten Meldungen verschieben (z. B. CoWaitForMultipleHandles), und bei lange dauernden Operationen generell Meldungen verschieben.


Ich konnte das Problem schon eingrenzen und bin der Meinung das der UI Thread meine View nicht aktualisieren kann.
Meinen ganzen Code poste ich hier nicht da es den Rahmen sprengen würde. Aber hier mal eine Skizze und einen Teil des Codes.

user defined image

Die MainView (WPF) enthält zwei Views.
Das ViewModel von MainView beinhaltet die Propertys für View 2 sowie eine eigene Property für das ViewModel der View3.
Sobald man in View2 etwas anklickt wird ein neues ViewModel für View3 erstellt und an die Property gebunden (+ OnPropertyChanged)
Problem ist, das eine async/await Methode in ViewModel 3 augerufen wird und eine Property in ViewModel3 ändert, welche auf View3 angezeigt werden soll.
Dadurch entsteht ein Deadlock da, so denke ich, der Thread von View3 nicht auf den UI Thread zugreifen kann.

Im Folgenden der C# Code.

Hier eine stark gekürzte Version des MainViewModel (ich hoffe man versteht was gemeint ist)

ausblenden volle Höhe 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:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
 public class MainViewModel : BaseViewModel       //ViewModel für MainView und View 2
    {
    
    public MainViewmodel(){
        SelectedProposalChanged += OnSelectedProposalChanged;
    }
    
    protected event EventHandler SelectedProposalChanged;
    
    //[...]
       private CodingProposal _selectedCodingProposals;
        public CodingProposal SelectedCodingProposals                   //Gebunden an View 2 
        {
            get { return _selectedCodingProposals; }
            set
            {
                _selectedCodingProposals = value;
                OnPropertyChanged(() => SelectedCodingProposals);

                SelectedProposalChanged?.Invoke(this, EventArgs.Empty);
            }
        }
        
        
        private ProposalDetailViewModel _proposalDetailViewDataContext;
        public ProposalDetailViewModel ProposalDetailViewDataContext        //ViewModel für View 3
        {
            get { return _proposalDetailViewDataContext; }
            set
            {
                _proposalDetailViewDataContext = value;
                OnPropertyChanged(() => ProposalDetailViewDataContext);
            }
        }
        //[...]

        /// <summary>
        /// Is fired when selected coding proposal is changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnSelectedProposalChanged(object sender, EventArgs e)
        {
            var handler = SelectedProposalChanged;
            if (handler == nullreturn;

                ProposalDetailViewDataContext = (SelectedCodingProposals == null)
                    ? new ProposalDetailViewModel(Guid.Empty, string.Empty, SearchParameter)
                    : new ProposalDetailViewModel(SelectedCodingProposals.AccessKey, SelectedCodingProposals.Label, SearchParameter);
        }
        
  }


Hier das ViewModel für View3 (komplett)
Nach dem folgendem Teil friert das UI ein und es folgt kurze Zeit später die Fehlermeldung.
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
             await Application.Current.Dispatcher.BeginInvoke(
                    new Action(() =>
                    {
                        Message = optionExtentsionText;
                    }));

Irgendwie muss ich den UI Thread erreichen und die View aktualisieren lassen.
Ich weiß bloß nicht wie meine Versuche scheiterten.

ausblenden volle Höhe 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:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
using System;
using System.Windows;
using *.Client.ServiceRepositories;
using Prism.Commands;
using Prism.Mvvm;

namespace bla.bla.Client.GUI.ViewModels
{
    public class ProposalDetailViewModel : BindableBase
    {

        public ProposalDetailViewModel(Guid accesskey, string title, string searchtext)
        {
            Title = title;
            SearchText = searchtext;
            GetData(accesskey);

            CloseCommand = new DelegateCommand<Window>(Excecute_Close);
        }

        public ProposalDetailViewModel(string message, string title, string searchtext)
        {
            Title = title;
            Message = message;
            SearchText = searchtext;

            CloseCommand = new DelegateCommand<Window>(Excecute_Close);
        }

        public DelegateCommand<Window> CloseCommand { get; private set; }


        private string _title;

        public string Title
        {
            get { return _title; }
            set
            {
                _title = value;
                OnPropertyChanged(() => Title);
            }
        }

        private string _message;

        public string Message
        {
            get { return _message; }
            set
            {
                _message = value;
                OnPropertyChanged(() => Message);
            }
        }

        private string _searchText;

        public string SearchText
        {
            get { return _searchText; }
            set
            {
                _searchText = value;
                OnPropertyChanged(() => SearchText);
            }
        }



        private async void GetData(Guid accesskey)
        {
            try
            {
                string optionExtentsionText = "keine Information vorhanden";

                if (accesskey != Guid.Empty)
                {

                    var optionExtension = Repository.Instance.GetOptionExtension(accesskey);
                    await optionExtension;

                    //Prüfe ob es Informationen gibt und ersetze diese
                    if (!string.IsNullOrEmpty(optionExtension.Result))
                        optionExtentsionText = optionExtension.Result;
                }


                await Application.Current.Dispatcher.BeginInvoke(
                    new Action(() =>
                    {
                        Message = optionExtentsionText;
                    }));
            }
            catch (Exception exception)
            {
                Repository.Instance.WriteClientException(exception);
            }
        }


        public void Excecute_Close(Window window)
        {
            window?.Close();
        }

    }
}



Ich freue mich auf Hilfe.

Vielen Dank!
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 15.09.16 12:50 
Hallo und :welcome:,

wofür brauchst du denn überhaupt den Dispatch-Invoke-Aufruf dort?
Wird die Methode GetData aus einem anderen Thread heraus aufgerufen?

PS: optionExtentsionText hat einen kleinen Rechtschreibfehler...
floho Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Do 15.09.16 14:12 
Hey :wave:

Oky.. ups da hab ich wohl doch was vergessen zu erwähnen.
Also das Repository ruft eine Method auf dem Webservice auf. (WCF mit SOAP und auch asynchron)

Wie man erkennen kann wird das ViewModel, die zugehörige Property heißt ProposalDetailViewDataContext, erst dann erstellt, sobald das Event SelectedProposalChanged gefeuert wird.

Während dem Konstruktor wird die Funktion GetData(accessKey) aufgerufen welche über eine Singleton Instanz den Webservice abfragt.
Man könnte auch anstatt
ausblenden C#-Quelltext
1:
2:
                    var optionExtension = Repository.Instance.GetSchemeOptionExtension(accesskey);
                    await optionExtension;

folgendes Schreiben:
ausblenden C#-Quelltext
1:
2:
                    Task<string> optionExtension = Repository.Instance.GetSchemeOptionExtension(accesskey);
                    await optionExtension;


Explizit mit Threads arbeite ich nicht... aber halt mit ... Tasks :-)



zum Dispatcher Invoke aufruf:
Das war einfach der letzte Stand meine ausprobierens.

Hatte gelesen um auf den UI Thread zugreifen zu können ruft man Application.Current.Dispatcher.Invoke auf.
Entweder mache ich etwas falsch.. oder es ist schlichtweg der falsche Ansatz.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 15.09.16 15:44 
Aber du rufst die Methode doch gar nicht asynchron (aus dem Konstruktor) auf (auch wenn du sie als async gekennzeichnet hast) - der Compiler müßte dir aber auch eine entsprechende Warnung anzeigen.

Was passiert denn, wenn du den Dispatch-Invoke-Aufruf wegläßt (außer, daß die Methode sowieso solange blockiert, wie der WebService-Aufruf dauert)?

Es ist aber keine gute Idee, das ViewModel mit Multithreading-Sachen zu vermischen (dieses dient nur dazu Daten für die UI [per DataBinding] zur Verfügung zu stellen).
Erstelle dir dafür ein Service-Klasse, welche dann Teil der BusinessLogik ist.

PS: Mittels Dispatcher.CheckAccess kannst du überprüfen, ob überhaupt ein (Begin)Invoke-Aufruf nötig ist.
floho Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Do 15.09.16 16:04 
von Application.Current.Dispatcher.CheckAccess() bekomme ich ein true zurück. Das bedeutet also das ich mit dem Application.Current.Dispatcher.Invoke gar nicht so verkehrt liege ?!

Lass es mich mal so erklären:

MainView <- Propertys in MainViewModel
View2 <- Propertys in MainViewModel
MainViewModel <- Propertys befüllen von Repository (asynchron)

Die Property ProposalDetailViewDataContext ist im MainViewModel und ist über das Binding gebunden an View3, welche in der MainView liegt.

View3 <- Propertys in ProposalDetailViewModel
ProposalDetailViewModel <- Propertys befüllen von Repository (asynchron)

Die als async gekennzeichnete Methode GetData(Guid accesskey) beinhaltet den Task der vom Repository gestartet wird. Hier die beiden Methoden vom Repository.
methode Open() öffnet die Verbindung zum Webservice und Close() schließt diese wieder.

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:
        private  async Task<T> Get<T>(Func<Task<T>> actionTask)
        {
            try
            {
                Open();
                return await actionTask();
            }
            catch (Exception exception)
            {
                WriteClientException(exception);
                throw;
            }
            finally
            {
                Close();
            }
        }

        public  async Task<string> GetSchemeOptionExtension(Guid accessKey)
        {
            var result = Get(() => _client.GetSchemeOptionExtensionAsync(accessKey));
            await result;

            return result.Result;
        }


Ich wüsste nicht was ich hierbei falsch ist?!
-> Wohlgemerkt, in meinen anderen Views funktioniert der Aufruf vom Konstruktor nach GetData(). Was aber nicht heißt das es nicht unsauber programmiert ist.
Mir geht es erstmal um das Deadlock Problem.

Ich entsuhldige mich jetzt erstmal dafür das ich hier unterschiedliche Methodennamen stehen habe. Dachte mir am Anfang die komplizierten und verwirrenden, innerhäuslichen Fachbegriffe erspare ich euch und verkürze sie einfach ;-)

Wie schon gesagt, dass mit dem Dispatcher war nur ein Test!!
Hier liegt aber auch das Problem. Sobald der Debugger die Zeile Message = optionExtentsionText; durchlaufen hat, friert die Applikation ein.

Moderiert von user profile iconTh69: C#-Tags hinzugefügt
Moderiert von user profile iconTh69: B- durch C#-Tags ersetzt
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 15.09.16 16:15 
floho hat folgendes geschrieben:
von Application.Current.Dispatcher.CheckAccess() bekomme ich ein true zurück. Das bedeutet also das ich mit dem Application.Current.Dispatcher.Invoke gar nicht so verkehrt liege ?!

Nein, schau dir das Beispiel im Link an, bei true braucht man kein Invoke, da man schon im UI-Thread ist (was ich dir auch schon mit dem ersten Satz aus meinem vorherigen Beitrag hab mitteilen wollen).

PS: bin jetzt ersteinmal mind. 2 Stunden weg, melde mich aber evtl. heute abend noch mal...
floho Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Fr 16.09.16 11:12 
Problem gelöst.

View2 und View3 waren durch einen GridSplitter getrennt. Als Standard war View3 nicht zu sehen.
Wenn View3 zu sehen ist tritt der Fehler nicht auf.

Ich frage nun eine bestimmte "Width" ab und lade erst dann die Daten.

Vielen Dank für die Hilfe !
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Fr 16.09.16 15:14 
Das erscheint mir aber nicht wirklich als die Behebung des ursächlichen Fehlers...