BackgroundWorker OnWorkCompleted gooi kruisdraad-uitsondering
-
03-07-2019 - |
Vra
Ek het 'n eenvoudige UserControl vir databasisblaai, wat 'n kontroleerder gebruik om die werklike DAL-oproepe uit te voer.Ek gebruik a BackgroundWorker
om die swaar opheffing uit te voer, en op die OnWorkCompleted
gebeurtenis Ek heraktiveer sommige knoppies, verander a TextBox.Text
eiendom en verhoog 'n geleentheid vir die ouervorm.
Vorm A hou my UserControl.Wanneer ek op een of ander knoppie klik wat vorm B oopmaak, selfs al doen ek niks "daar" en maak dit net toe, en probeer om die volgende bladsy van my databasis in te bring, die OnWorkCompleted
word op die werkersdraad geroep (en nie my Hoofdraad nie), en gooi 'n kruisdraad-uitsondering.
Op die oomblik het ek 'n tjek bygevoeg vir InvokeRequired
by die hanteerder daar, maar is nie die hele punt van OnWorkCompleted
moet op die Hoofdraad genoem word?Hoekom sal dit nie werk soos verwag nie?
EDIT:
Ek het daarin geslaag om die probleem te beperk tot arcgis en BackgroundWorker
.Ek het die volgende oplossing wat 'n opdrag by arcmap voeg, wat 'n eenvoudige oopmaak Form1
met twee knoppies.
Die eerste knoppie loop a BackgroundWorker
wat vir 500 ms slaap en 'n teller opdateer.In die RunWorkerCompleted
metode waarna dit nagaan InvokeRequired
, en werk die titel op om te wys of die metode oorspronklik binne die hoofdraad of die werkdraad geloop het.Die tweede knoppie maak net oop Form2
, wat niks bevat nie.
Aanvanklik het al die oproepe na RunWorkerCompletedare
word binne die hoofdraad gemaak (soos verwag - dit is die hele punt van die RunWorkerComplete-metode, ten minste volgens wat ek verstaan uit die MSDN aan BackgroundWorker
)
Na oop- en toemaak Form2
, die RunWorkerCompleted
word altyd op die werkersdraad geroep.Ek wil byvoeg dat ek hierdie oplossing vir die probleem net kan laat (kyk vir InvokeRequired
in die RunWorkerCompleted
metode), maar ek wil verstaan hoekom dit teen my verwagtinge gebeur.In my "regte" kode wil ek altyd weet dat die RunWorkerCompleted
metode word op die hoofdraad genoem.
Ek het daarin geslaag om die probleem vas te stel by die form.Show();
bevel in my BackgroundTesterBtn
- as ek gebruik ShowDialog()
in plaas daarvan kry ek geen probleem nie (RunWorkerCompleted
loop altyd op die hoofdraad).Ek moet gebruik Show()
in my ArcMap-projek, sodat die gebruiker nie aan die vorm gebind sal wees nie.
Ek het ook probeer om die fout op 'n normale WinForms-projek te reproduseer.Ek het 'n eenvoudige projek bygevoeg wat net die eerste vorm sonder ArcMap oopmaak, maar in daardie geval kon ek nie die fout reproduseer nie - die RunWorkerCompleted
het op die hoofdraad gehardloop, of ek gebruik het Show()
of ShowDialog()
, voor en na opening Form2
.Ek het probeer om 'n derde vorm by te voeg om as 'n hoofvorm voor my op te tree Form1
, maar dit het nie die uitslag verander nie.
Hier is my eenvoudige sln (VS2005sp1) - dit vereis
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI (9.2.3.1380)
Oplossing
Dit lyk soos 'n fout:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930
http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx
So ek stel voor om die koeëlvaste (pseudokode) te gebruik:
if(control.InvokeRequired)
control.Invoke(Action);
else
Action()
Ander wenke
Is nie die hele punt van
OnWorkCompleted
moet op die Hoofdraad genoem word?Hoekom sal dit nie werk soos verwag nie?
Nee dit is nie.
Jy kan nie sommer enige ou ding op enige ou draad gaan hardloop nie.Drade is nie beleefde voorwerpe wat jy bloot kan sê "hardloop dit asseblief".
'n Beter geestelike model van 'n draad is 'n goederetrein.Sodra dit aan die gang is, is dit op sy eie spoor.Jy kan nie sy koers verander of dit stop nie.As jy dit wil beïnvloed, moet jy óf wag totdat dit by die volgende treinstasie kom (bv.laat dit handmatig kyk vir sommige gebeurtenisse), of laat dit ontspoor (Thread.Abort
en CrossThread-uitsonderings het baie dieselfde gevolge as om 'n trein te ontspoor ...pasop!).
Winforms kontroles soortvan ondersteun hierdie gedrag (Hulle het Control.BeginInvoke
wat jou toelaat om enige funksie op die UI-draad te laat loop), maar dit werk net omdat hulle 'n spesiale haak in die Windows UI-boodskappomp het en 'n paar spesiale hanteerders skryf.Om met bogenoemde analogie te gaan, hul trein meld by die stasie in en soek periodiek na nuwe aanwysings, en jy kan daardie fasiliteit gebruik om dit jou eie aanwysings te plaas.
Die BackgroundWorker
is ontwerp om algemene doel te wees (dit kan nie aan die Windows GUI gekoppel word nie) sodat dit nie die vensters kan gebruik nie Control.BeginInvoke
kenmerke.Dit moet aanvaar dat jou hoofdraad 'n onstuitbare 'trein' is wat sy eie ding doen, so die voltooide gebeurtenis moet in die werkersdraad loop of glad nie.
Soos jy egter winforms gebruik, in jou OnWorkCompleted
hanteerder, kan jy die venster kry om uit te voer 'n ander terugbel met behulp van die BeginInvoke
funksionaliteit wat ek hierbo genoem het.Soos hierdie:
// Assume we're running in a windows forms button click so we have access to the
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
var b = new BackgroundWorker();
b.DoWork += ... blah blah
// attach an anonymous function to the completed event.
// when this function fires in the worker thread, it will ask the form (this)
// to execute the WorkCompleteCallback on the UI thread.
// when the form has some spare time, it will run your function, and
// you can do all the stuff that you want
b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
b.RunWorkerAsync(); // GO!
}
void WorkCompleteCallback()
{
Button.Enabled = false;
//other stuff that only works in the UI thread
}
Moet ook nie hierdie vergeet nie:
Jou RunWorkerCompleted-gebeurtenishanteerder moet altyd die Fout- en Gekanselleer-eienskappe nagaan voordat jy toegang tot die Resultaat-eienskap kry.As 'n uitsondering geopper is of as die operasie gekanselleer is, veroorsaak toegang tot die Resultaat-eiendom 'n uitsondering.
Die BackgroundWorker
kontroleer of die afgevaardigde instansie na 'n klas wys wat die koppelvlak ondersteun ISynchronizeInvoke
.Jou DAL-laag implementeer waarskynlik nie daardie koppelvlak nie.Normaalweg sal jy die BackgroundWorker
op 'n Form
, wat wel daardie koppelvlak ondersteun.
In die geval dat jy die BackgroundWorker
vanaf die DAL-laag en van daar af die UI wil opdateer, het jy drie opsies:
- jy sal die bly bel
Invoke
metode - die koppelvlak te implementeer
ISynchronizeInvoke
op die DAL-klas, en herlei die oproepe met die hand (dit is net drie metodes en 'n eienskap) - voordat u die aanroep
BackgroundWorker
(dus, op die UI-draad), om te belSynchronizationContext.Current
en om die inhoudsinstansie in 'n instansieveranderlike te stoor.DieSynchronizationContext
sal dan vir jou dieSend
metode, wat presies sal doen watInvoke
doen.
Die beste benadering om probleme met kruisdraad in GUI te vermy, is om gebruik SynchronizationContext.