2012-04-11 10 views
1

Ich habe einen Hintergrund-Thread, der die Benutzeroberfläche aktualisiert, sobald es fertig ist. Ich habe versucht, so sicher wie möglich zu sein, damit ich nicht die GUI anrufe, die Disposed ist.UI vom Hintergrund Thread aktualisieren - woher weiß ich, dass meine GUI nicht entsorgt wird?

void DoInBackground() 
{ 
    try 
    { 
     string result = ServerSideProcess(); 
*  if (!IsDisposed && !Disposing) 
*   BeginInvoke(new StringDelegate(UpdateText), result); 
    } 
    catch (Exception ex) 
    { 
*  if (!IsDisposed && !Disposing) 
*   BeginInvoke(new VoidDelegate(UpdateFailed)); 
    } 
} 


void UpdateText(string txt) 
{ 
    if (!IsDisposed && !Disposing) 
     textbox1.Text = txt; 
} 

void UpdateFailed() 
{ 
    if (!IsDisposed && !Disposing) 
     textbox1.Text = "failed to get data from server"; 
} 

override Dispose(bool disposing) 
{ 
    if (disposing) 
    { 
     if (components != null) 
      components.Dispose(); 
    } 
    base.Dispose(disposing); 
} 

Ich denke, dass ich genug, um in die GUI-Methoden sicher bin - Entsorgen() wird nicht aufgerufen, während ich innen Update (string) oder UpdateFailed(), weil sie laufen beide im selben Thread bin, also nehme ich an, dass die Überprüfung auf IsDisposing und spätere Ausführung gut genug ist. Aber wie kann ich sicher sein, dass die Teile in (*) kein Dispose() dazwischen bekommen, was dazu führen wird, dass BeginInvoke für eine disponierende Klasse aufgerufen wird und schließlich ein Anwendungsabsturz?

Ich testete es, indem ich Thread.Sleep (2000) zwischen den (*) Teilen hinzufügte, Breakpoints vor und nach dem Thread.Sleep platzierte und aus dem Steuerelement löste, um Dispose() d zu erhalten, bevor BeginInvoke erreicht wurde. das Ergebnis - meine Bewerbung ist abgestürzt. Wie kann ich wissen, dass die Laufzeit mir dieses unglückliche Kontextwechsel-Szenario nicht geben wird?

Antwort

2

Ich würde das eine außergewöhnliche Bedingung nennen - verwenden Sie einen try/catch um die BeginInvokes, um die Ausnahme, die ausgelöst wird, zu erfassen und explizit zu behandeln.

+0

Es gibt einige unglückliche Macken in der Art, wie 'InvokeRequired', 'BeginInvoke' usw. implementiert werden, so dass es keine rennfreie Freigabe gibt, wenn Sie keine gesperrte Flagge innerhalb Ihrer' Dispose'-Routine setzen können Möglichkeit, ein 'BeginInvokeUnlessWindowIsGone' auszuführen. Sie müssen nur das 'BeginInvoke' machen und die Ausnahme verschlucken. – supercat

+0

@supercat stimmte zu, deshalb denke ich, es ist ein Ausnahmefall (und so ist der Versuch/Fang gerechtfertigt). –

+0

Von einer Anruferseite, ja. Es ist ein kleines Design, aber der Gestank kommt von dem Versagen des Frameworks zu erkennen, dass viele BeginInvokes (und Ereignisse) zugunsten des Ziels statt des Aufrufers geschehen; Ähnliches passiert bei Ereignissen (z. B. weil es kein "WeakEvent" oder "WeakDelegate" gibt). – supercat

4

Es sieht aus wie ein Problem, das vom Backgroundworker kostenlos gelöst wird.

Irgendein Grund, es nicht zu benutzen?

+0

Der BackgroundWorker verwendet Threadpool-Threads, nicht wahr? Einige Tasks sind besser mit einem eigenen Thread ausgestattet, entweder weil sie viel Zeit mit der Blockierung verbringen oder weil sie ThreadStatic-Daten verwenden, die verschwinden sollten, wenn sie fertig sind. – supercat

+0

'Stehlen' eines Threads aus dem Pool ist akzeptabel. ThreadStatic/ThreadLocal sollte trotzdem vermieden werden. –

+0

Wenn ein Thread aus dem Pool gestohlen und für eine lange Zeit gehalten wird, wird dies die Dinge nicht beeinflussen, aber (1) wie viele Dinge können "einen" Thread hoggen, bevor es ein Problem wird, und (2) wenn der Pool passiert um gesichert zu werden, kann die Anforderung eines Threads beliebig verzögert werden. Was ThreadStatic betrifft, ist es nicht wunderbar, aber es gibt Kontexte, in denen es die am wenigsten böse Art ist, Dinge zu tun. – supercat

0

Die folgende besagt mir keine Fehlermeldung angezeigt, wenn sie ausgeführt werden:

BackgroundWorker bw = new BackgroundWorker(); 

public Form1() 
{ 
    InitializeComponent(); 

    bw.DoWork += bw_DoWork; 
    bw.RunWorkerCompleted += bw_RunWorkerCompleted; 

    Shown += Form1_Shown; 
} 

void Form1_Shown(object sender, EventArgs e) 
{ 
    bw.RunWorkerAsync(); 
    Thread.Sleep(1000); 
    Dispose(); 
} 

void bw_DoWork(object sender, DoWorkEventArgs e) 
{ 
    //This is your second thread. 
    Thread.Sleep(2000); 
} 

void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    //This runs when the second thread is finished. Update here. 
    Text = "Done"; 
} 
+0

großartig, getestet und es hat tatsächlich funktioniert, danke! – user1327082

0

In einigen Fällen können Sie möglicherweise, indem die Dispose Routine für die Steuerung erwerben eine Sperre, einen Flag Clean Shutdown erreichen und löse das Schloss. Ihre Update-Routine sollte die Sperre erhalten, tun Sie die BeginInvoke, wenn das Flag nicht festgelegt ist, und geben Sie die Sperre frei. Der Hauptzweck der Sperre besteht darin, sicherzustellen, dass die Steuerung, sobald sich die Aktualisierungsroutine entschieden hat, die BeginInvoke anzurufen, nicht entsorgt wird, bis die Aktualisierung erfolgt.

Das gesagt worden ist, in vielerlei Hinsicht denke ich, es ist sauberer, nur die BeginInvoke zu tun und schlucken die Ausnahme, die auftreten wird, wenn die Kontrolle von unter Ihnen entsorgt wird. Es sollte wirklich eine TryBeginInvoke, die eine BeginInvoke tun würde und True zurückgeben, wenn die Kontrolle lebendig ist und sonst False zurückgeben, aber leider gibt es keine rennfreie Möglichkeit, eine zu erstellen, ohne sich selbst in den Dispose Prozess der Steuerung zu injizieren oder eine Ausnahme auftreten lassen und sie ersticken.