[C#] Cross thread error

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Anoniem: 351336

Topicstarter
Beste tweakers,

Ik roep in mijn programma een functie aan die een mp3 afspeelt als er op de serialport een bepaalde waarde binnen komt. Ik gebruik de serialPort_DataReceived event om de functie aan te roepen.
Hierbij krijg ik de error "System.InvalidOperationException was unhandled
Message=Het is niet toegestaan een bewerking uit te voeren via verschillende threads: er werd vanaf een andere thread toegang gekregen tot het besturingselement Main dan de thread waarop het element is gemaakt."

Mijn code is als volgt
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[DllImport("winmm.dll")]
private static extern long mciSendString(string strCommand, StringBuilder strReturn, int iReturnLength, IntPtr hwndCallback);

delegate void MP3CallBack(string sMp3Path);
void playMP3(string sMp3Path)
{
 if (this.InvokeRequired)
 {
  MP3CallBack d = new MP3CallBack(playMP3);
  this.Invoke(d, new object[] { sMp3Path });
 }
 else
 {
  sSoundCommand = "open \"" + sMp3Path + "\" type mpegvideo alias MediaFile";
  mciSendString(sSoundCommand, null, 0, IntPtr.Zero);
  sSoundCommand = "play MediaFile notify";
  mciSendString(sSoundCommand, null, 0, this.Handle);
 }
}


De fout staat bij "mciSendString(sSoundCommand, null, 0, this.Handle);"
Als ik google vind ik alleen maar dat je moet invoke moet gebruiken,wat ik al doe.
Ik snap niet waarom ik een fout krijgt dat er van een andere thread wordt aangeroepen want ik invoke toch al?

Het enige wat ik nog kan bedenken is dat this.InvokeRequired niet bij de juiste control kijkt of er een invoke nodig is.

Hebben jullie tips waar het aan kan liggen?

Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Als de fout daadwerkelijk op de mciSendString komt ( wat mij eigenlijk onwaarschijnlijk lijkt aangezien dat op een native dll lijkt ), dan word die fout dus in de winmn.dll gegooid, en dan moet je dus daar op de fout zoeken.

Je controleert namelijk op je huidige control ( waarschijnlijk een Form ) of daar InvokeRequired is, maar het kan natuurlijk best zijn dat de library of de daarbij behorende resources op een andere thread gemaakt zijn. Kijk eens goed naar de stacktrace van je Exception.

[ Voor 4% gewijzigd door Woy op 14-06-2011 14:12 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Woy schreef op dinsdag 14 juni 2011 @ 14:10:
Als de fout daadwerkelijk op de mciSendString komt ( wat mij eigenlijk onwaarschijnlijk lijkt aangezien dat op een native dll lijkt ), dan word die fout dus in de winmn.dll gegooid, en dan moet je dus daar op de fout zoeken.
Er word nergens op de return value gechecked dus die error komt niet daar vandaan. Waarschijnlijk zit het probleem er gewoon in dat een non-gui thread een gui component will accessen en dat moet (volgens mijn beperkte .Net kennis) met behulp van BeginInvoke().

Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Het is inderdaad een exception die typisch gegooid word als je een GUI element buiten de GUI thread benadert. In het code voorbeeld Invoked hij dan ook netjes, maar volgens mij is dat helemaal niet nodig voor een externe library, en als dat wel het geval is, dan is InvokeRequired van het huidige component niet de juiste check, aangezien de thread die het component gecreëerd heeft niet perse gelijk hoeft te zijn als de thread die de library gemaakt heeft.

Maar het lijkt mij inderdaad onwaarschijnlijk dat die exception in mciSendString gegooid word. Waarschijnlijk is deze code niet exact gelijk aan de code die de TS daadwerkelijk draait, of hij heeft de exception verkeerd geïnterpreteerd.

[ Voor 27% gewijzigd door Woy op 14-06-2011 14:42 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

Anoniem: 351336

Topicstarter
Als ik de invoke weghaal krijg ik inderdaad dezelfde fout.
De code wijkt inderdaad ook een klein beetje af.
C#:
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
        void playMP3(string sMp3Path)
        {
         /*   if (this.InvokeRequired)
            {
                MP3CallBack d = new MP3CallBack(playMP3);
                this.Invoke(d, new object[] { sMp3Path });
            }
            else
            {*/
                if (!sMp3Path.Equals(sOldMp3Path))
                {
                    sSoundCommand = "close MediaFile";
                    mciSendString(sSoundCommand, null, 0, IntPtr.Zero);
                }

                if (sSoundCommand.Equals("close MediaFile"))
                {
                    sOldMp3Path = sMp3Path;
                    sSoundCommand = "open \"" + sMp3Path + "\" type mpegvideo alias MediaFile";
                    mciSendString(sSoundCommand, null, 0, IntPtr.Zero);
                    sSoundCommand = "play MediaFile notify";
                    mciSendString(sSoundCommand, null, 0, this.Handle);
                }
            //}
        }  

Maar het lijkt me niet dat het probleem daar vandaan komt.

De stack trace geeft het volgende
bij System.Windows.Forms.Control.get_Handle()
bij GroundStation.Main.playMP3(String sMp3Path) in ...\GroundStation\Main.cs:regel 455
bij GroundStation.Main.serialPort_DataReceived(Object sender, SerialDataReceivedEventArgs e) in ...\GroundStation\Main.cs:regel 120

Maar ik weet eerlijk gezegd niet wat ik daaruit kan opmaken...

Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Dat je de Handle van je control vanuit de verkeerde thread opvraagt :) En dus playMp3 in een andere thread aan moet roepen.

[ Voor 27% gewijzigd door PrisonerOfPain op 14-06-2011 15:45 ]


Acties:
  • 0 Henk 'm!

Anoniem: 351336

Topicstarter
PrisonerOfPain schreef op dinsdag 14 juni 2011 @ 15:44:
Dat je de Handle van je control vanuit de verkeerde thread opvraagt :) En dus playMp3 in een andere thread aan moet roepen.
Is dat niet gewoon wat ik deed voor ik de invoke weg gehaald heb?

De vraag is dan hoe laat ik hem invoken op het moment dat het nodig is. Van Handle kan je geen invokerequired opvragen

[ Voor 16% gewijzigd door Anoniem: 351336 op 14-06-2011 17:08 ]


Acties:
  • 0 Henk 'm!

  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 07-07 16:14
Anoniem: 351336 schreef op dinsdag 14 juni 2011 @ 15:39:
De stack trace geeft het volgende
bij System.Windows.Forms.Control.get_Handle()
bij GroundStation.Main.playMP3(String sMp3Path) in ...\GroundStation\Main.cs:regel 455
bij GroundStation.Main.serialPort_DataReceived(Object sender, SerialDataReceivedEventArgs e) in ...\GroundStation\Main.cs:regel 120

Maar ik weet eerlijk gezegd niet wat ik daaruit kan opmaken...
Welke regels zijn regel 455 en 120? Dat is wel handig om te weten om die melding te begrijpen :P

Acties:
  • 0 Henk 'm!

  • alwinuzz
  • Registratie: April 2008
  • Laatst online: 10:13
Gokje dat regel 22 onder "De code wijkt inderdaad ook een klein beetje af." regel 455 is :) Waar this.Handle staat.

Zou het helpen als je je code zo schrijft dat serialPort_DataReceived altijd een Invoke(playMP3) doet?
Invoke gebruiken terwijl het niet nodig is kan geen kwaad vziw.

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 08-07 21:47
Kan het zijn dat je last hebt van dit effect?
This means that InvokeRequired can return false if Invoke is not required (the call occurs on the same thread), or if the control was created on a different thread but the control's handle has not yet been created.

In the case where the control's handle has not yet been created, you should not simply call properties, methods, or events on the control. This might cause the control's handle to be created on the background thread, isolating the control on a thread without a message pump and making the application unstable.

You can protect against this case by also checking the value of IsHandleCreated when InvokeRequired returns false on a background thread. If the control handle has not yet been created, you must wait until it has been created before calling Invoke or BeginInvoke. Typically, this happens only if a background thread is created in the constructor of the primary form for the application (as in Application.Run(new MainForm()), before the form has been shown or Application.Run has been called.

[ Voor 4% gewijzigd door farlane op 14-06-2011 19:09 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

Anoniem: 351336

Topicstarter
alwinuzz schreef op dinsdag 14 juni 2011 @ 19:00:
Gokje dat regel 22 onder "De code wijkt inderdaad ook een klein beetje af." regel 455 is :) Waar this.Handle staat.

Zou het helpen als je je code zo schrijft dat serialPort_DataReceived altijd een Invoke(playMP3) doet?
Invoke gebruiken terwijl het niet nodig is kan geen kwaad vziw.
Heb ik geprobeerd maar dan krijg ik een error dat je invoked als het niet nodig is...
gerrymeistah schreef op dinsdag 14 juni 2011 @ 17:56:
[...]
Welke regels zijn regel 455 en 120? Dat is wel handig om te weten om die melding te begrijpen :P
Regel 120: playMP3("enginefailure.mp3");
Regel 455: mciSendString(sSoundCommand, null, 0, this.Handle);

[ Voor 25% gewijzigd door Anoniem: 351336 op 15-06-2011 10:31 ]


Acties:
  • 0 Henk 'm!

Anoniem: 351336

Topicstarter
@farlane Dat was het inderdaad! Ik check nu in de functie playMP3 op this.IsHandleCreated en zo niet dan wordt er niks uit gevoerd.
C#:
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
        void playMP3(string sMp3Path)
        {
            if (this.IsHandleCreated)
            {
                if (this.InvokeRequired)
                {
                    MP3CallBack d = new MP3CallBack(playMP3);
                    this.Invoke(d, new object[] { sMp3Path });
                }
                else
                {
                    if (!sMp3Path.Equals(sOldMp3Path))
                    {
                        sSoundCommand = "close MediaFile";
                        mciSendString(sSoundCommand, null, 0, IntPtr.Zero);
                    }

                    if (sSoundCommand.Equals("close MediaFile"))
                    {
                        sOldMp3Path = sMp3Path;
                        sSoundCommand = "open \"" + sMp3Path + "\" type mpegvideo alias MediaFile";
                        mciSendString(sSoundCommand, null, 0, IntPtr.Zero);
                        sSoundCommand = "play MediaFile notify";
                        mciSendString(sSoundCommand, null, 0, this.Handle);
                    }
                }
            }
        } 


Bedankt allemaal voor de hulp!

[ Voor 86% gewijzigd door Anoniem: 351336 op 15-06-2011 10:46 ]


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Heb je de post van farlane gelezen? Dat lijkt me het meest logisch, en anders komt je code hier waarschijnlijk gewoon niet overeen met de code die je hebt.

Ah je hebt je post ge-edit.

[ Voor 10% gewijzigd door Woy op 15-06-2011 10:47 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

Heb je die hele notify wel nodig? Als je die niet gebruikt hoef je de handle ook niet mee te geven...

En ik vind geneste if's afgrijselijk, vind je dit niet veel leesbaarder, zeker wanneer voorzien van commentaar?
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
            if (!this.IsHandleCreated)
                return;
            
            if (this.InvokeRequired)
            {
                MP3CallBack d = new MP3CallBack(playMP3);
                this.Invoke(d, new object[] { sMp3Path });
                return;
            }

            if (!sMp3Path.Equals(sOldMp3Path))
            {
                sSoundCommand = "close MediaFile";
                mciSendString(sSoundCommand, null, 0, IntPtr.Zero);
            }

            if (sSoundCommand.Equals("close MediaFile"))
            {
                sOldMp3Path = sMp3Path;
                sSoundCommand = "open \"" + sMp3Path + "\" type mpegvideo alias MediaFile";
                mciSendString(sSoundCommand, null, 0, IntPtr.Zero);
                sSoundCommand = "play MediaFile notify";
                mciSendString(sSoundCommand, null, 0, this.Handle);
            }

https://oneerlijkewoz.nl
Het ergste moet nog komen / Het leven is een straf / Een uitgestrekte kwelling van de wieg tot aan het graf


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

CodeCaster schreef op woensdag 15 juni 2011 @ 15:04:
Heb je die hele notify wel nodig? Als je die niet gebruikt hoef je de handle ook niet mee te geven...

En ik vind geneste if's afgrijselijk, vind je dit niet veel leesbaarder, zeker wanneer voorzien van commentaar?
...
Dan zit je returns rond te strooien, dat is soms nogal onoverzichtelijk om alle mogelijk "paden" door je code te doorzien. Er is wat te zeggen voor 1 return statement per functie voor debuggen/tracen

-niks-


Acties:
  • 0 Henk 'm!

Anoniem: 351336

Topicstarter
CodeCaster schreef op woensdag 15 juni 2011 @ 15:04:
Heb je die hele notify wel nodig? Als je die niet gebruikt hoef je de handle ook niet mee te geven...

En ik vind geneste if's afgrijselijk, vind je dit niet veel leesbaarder, zeker wanneer voorzien van commentaar?
Zonder die notify speelt hij niet af.

Zo kan het inderdaad ook maar ik heb zelf niet z'n probleem met geneste if's als je het gewend bent is het prima te begrijpen na mij mening.
Pagina: 1