2

Ich habe ein kleines Problem mit dem Laden meiner Daten und Filtern Thread sicher.Bitte helfen Sie mir diesen Code Thread sicher machen

Der folgende Code für die Basisklasse meines Steuerelements, die die gesamte Datenpopulation durch einen BackgroundWorker behandelt. Dies führt dazu, dass der Fehler "this.DataWorker.RunWorkerAsync()" ausgelöst wird, der besagt, dass der BackgroundWorker beschäftigt ist.

/// <summary> 
/// Handles the population of the form data. 
/// </summary> 
/// <param name="reload">Whether to pull data back from the WebService.</param> 
public void Populate(bool reload) 
{ 
    if (!this.DataWorker.IsBusy) 
    { 

     // Disable the filter options 
     IvdSession.Instance.FilterManager.SetEnabledState(this.GetType(), false); 

     // Perform the population 
     this.DataWorker.RunWorkerAsync(reload); 

    } 
    else if (!reload) 
    { 
     // If the data worker is busy and this is a not reload, then something bad has happened (i.e. the filter has run during a reload.) 
     throw new InvalidOperationException("The DataWorker was busy whilst asked to reload."); 
    } 
} 

Der Code wird in zwei möglichen Orte genannt. Zum einen durch einen Timer auf der Form, dass die Steuerung auf:

auf die
public void Filter() 
{ 
    if (!m_BlockFilter) 
    { 
     IvdInstance.Main.CurrentBody.FirstRun = true; 
     IvdInstance.Main.CurrentBody.Populate(false); 
    } 
} 

Der Timer:

private void tmrAutoRefresh_Tick(object sender, EventArgs e) 
{ 
    if (!(this.CurrentBody == null)) 
    { 
     this.CurrentBody.Populate(true); 
    } 
} 

Und zweitens, jederzeit ein Benutzer eine Filteroption aus einer Reihe von Dropdown-Listen wählt Das Hauptformular wird alle 60 Sekunden ausgeführt und wird an die Populate-Methode übergeben. Passing reload als trues erzählt die Background, dass es einen neuen Satz von Daten aus dem WebService nach unten ziehen muss:

void dataWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 

    try 
    { 

     if (base.FirstRun) 
     { 
      base.CleanListView(); 
     } 

     if ((bool)e.Argument) 
     { 
      byte[] serialized = IvdSession.DataAccess.GetServiceCalls(IvdSession.Instance.Company.Description, IvdSession.Instance.Company.Password, null); 
      m_DataCollection = new DalCollection<ServiceCallEntity>(serialized); 
     } 

     List<ServiceCallEntity> collection = this.ApplyFilter(); 
     base.HandlePopulation<ServiceCallEntity>(collection, e); 

    } 
    catch (WebException ex) 
    { 
     // Ignore - Thrown when user clicks cancel 
    } 
    catch (System.Web.Services.Protocols.SoapException ex) 
    { 
     // Log error on server and stay transparent to user 
     base.LogError(ex); 
    } 
    catch (System.Data.SqlClient.SqlException ex) 
    { 
     // Inform user that the database is unavailable 
     base.HandleSystemUnavailable(ex); 
    } 

} 

Soweit ich bin mir dessen bewusst, tritt der Fehler auf, wenn ich es schaffe genau eine Filteroption klicken Gleichzeitig löst der Timer das Populationsereignis aus. Ich denke, es fehlt etwas an der Populate-Methode, d. H. Eine Sperre, aber ich bin nicht sicher, wie ich es in diesem Fall richtig verwenden soll.

Der Code ist gegenüber der Benutzereingabe bevorzugt. Wenn ein Benutzer eine Filteroption auswählt, sollte die automatische Aktualisierung blockiert werden. Wenn die automatische Aktualisierung ausgelöst wird, werden die Filteroptionen vorübergehend deaktiviert. Wenn sie gleichzeitig ausgelöst werden, sollte die Benutzereingabe Vorrang haben (wenn möglich).

Hoffe jemand kann helfen!

Antwort

2

zunächst eine Sperre um Ihre Populate Methode Körper hinzufügen:

private object _exclusiveAccessLock = new object(); 
public void Populate(bool reload) 
{ 
    lock (_exclusiveAccessLock) 
    { 
     // start the job 
    } 
} 

Dies wird Ihnen helfen, eine Race-Bedingung zu vermeiden (obwohl: wenn ich es richtig, da Sie ein Windows verwenden .Forms Timer, es wird immer aus dem Gui-Thread ausgelöst, so sollten sie nie genau zur gleichen Zeit ausgeführt werden).

Als nächstes bin ich mir nicht sicher, ob Sie die Ausnahme überhaupt werfen sollten. Sie können zum Beispiel ein zusätzliches Flag setzen, das Ihnen anzeigt, dass der Arbeiter noch nicht fertig ist, aber das sollte Ihnen IsBusy sowieso sagen.

Dann gibt es die m_BlockFilter Flagge.Ich kann nicht sehen, wo du es einstellst. Es sollte auch innerhalb der Sperre gesetzt werden, nicht im Hintergrund-Thread, da Sie in diesem Fall nicht sicher sein können, dass es nicht verzögert wird. Sie müssen auch das Feld volatile machen, wenn Sie es als Cross-Thread-Flag verwenden möchten.

+0

@Groo, weiß nicht die genaue Spezifikation von Windows, aber mit Multi-Cores, würden Sie nicht in der Lage sein, zwei Dinge gleichzeitig laufen zu lassen? –

+0

Ja, die Methode * sollte * threadsicher gemacht werden. Aber es gibt eine Regel, dass Sie die Gui-Elemente immer aus dem Gui-Thread aktualisieren müssen. Daher sorgt Windows.Forms.Timer dafür, den Ereignishandler der Gui-Thread-Warteschlange hinzuzufügen (er ruft ihn aus dem Gui-Thread auf), um Dinge zu vereinfachen. – Groo

+0

Hey, wen machst du Witze mit "weiß nicht die genaue Spezifikation von Windows"? :) – Groo

1

Siehe Thread Synchronization (C# Programming Guide):

public class TestThreading 
{ 
    private System.Object lockThis = new System.Object(); 

    public void Function() 
    { 

     lock (lockThis) 
     { 
      // Access thread-sensitive resources. 
     } 
    } 
} 

bearbeiten: Sie wollen nicht zwei Threads Populate Eingabe, so dass man etwas als Gebrüll tun könnte:

public void Populate(bool reload) 
{ 

    lock (lockThis) 
    { 
     // Disable the filter options 
     IvdSession.Instance.FilterManager.SetEnabledState(this.GetType(), false); 

     // do actual work. 
    } 

} 

EDIT2: Du hast gut etwas, das mit BackgroundWorker geht, also könnten Sie vielleicht etwas tun, um den anderen Thread warten zu lassen.

public void Populate(bool reload) 
{ 
    while (this.DataWorker.IsBusy) { 
     Thread.Sleep(100); 
    } 

    // Disable the filter options 
    IvdSession.Instance.FilterManager.SetEnabledState(this.GetType(), false); 

    // Perform the population 
    this.DataWorker.RunWorkerAsync(reload); 
} 
+0

Im Zusammenhang mit dem obigen Code, wo würde ich das verwenden? – GenericTypeTea

Verwandte Themen