2

J'ai un peu de difficulté à sécuriser le chargement et le filtrage de mes données.Aidez-moi à sécuriser ce thread de code

Le code suivant sur la classe de base de mon contrôle qui gère toute la population de données via un BackgroundWorker. Cela tend à jeter l'erreur sur "this.DataWorker.RunWorkerAsync()" en disant que BackgroundWorker est occupé.

/// <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."); 
    } 
} 

Le code est appelé dans deux places possibles. Tout d'abord par une minuterie sur la forme que le contrôle est:

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

Et d'autre part, chaque fois qu'un utilisateur sélectionne une option de filtre à partir d'un certain nombre de listes déroulantes:

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

La minuterie sur la La forme principale s'exécute toutes les 60 secondes et passe à la méthode Populate. En passant reload comme trues dit le BackgroundWorker qu'il doit tirer vers le bas une nouvelle série de données de la WebService:

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); 
    } 

} 

Pour autant que je sache, l'erreur se produit lorsque je parviens à cliquer sur une option de filtre exactement en même temps, le temporisateur déclenche l'événement de population. Je suppose qu'il manque quelque chose à la méthode Populate, c'est-à-dire un verrou, mais je ne suis pas sûr de savoir comment l'utiliser correctement dans ce cas.

Le code est préféré vers l'entrée de l'utilisateur. Si un utilisateur sélectionne une option de filtre, la mise à jour automatique doit être bloquée. Si la mise à jour automatique se déclenche, les options de filtre sont temporairement désactivées. S'ils tirent en même temps, l'entrée de l'utilisateur devrait être prioritaire (si possible).

Espérons que quelqu'un peut vous aider!

Répondre

2

D'abord, ajoutez un verrou autour de votre corps de la méthode Populate:

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

Cela vous aidera à éviter une condition de course (bien: si je me trompe pas, puisque vous utilisez Windows .Forms Timer, il tirera toujours du fil de Gui, ainsi ils ne devraient jamais être exécutés exactement en même temps).

Ensuite, je ne suis pas sûr si vous devriez jeter l'exception du tout. Vous pouvez, par exemple, définir un drapeau supplémentaire qui vous indique que le travailleur n'a pas encore terminé, mais c'est ce que IsBusy devrait vous dire de toute façon.

Ensuite, il y a le drapeau m_BlockFilter.Je ne peux pas voir d'où vous le définissez. Il doit également être défini dans le verrou, pas dans le fil d'arrière-plan, car dans ce cas, vous ne pouvez pas être certain qu'il ne sera pas retardé. Vous devez également créer le champ volatile si vous souhaitez l'utiliser comme indicateur d'inter-thread.

+0

@Groo, ne connais pas la spécification exacte de Windows, mais avec des multi-cœurs, ne seriez-vous pas en mesure d'avoir deux choses en cours d'exécution simultanément? –

+0

Oui, la méthode * devrait * être thread-safe. Mais il existe une règle selon laquelle vous devez toujours mettre à jour les éléments Gui depuis le thread Gui. Donc, Windows.Forms.Timer prend soin d'ajouter le gestionnaire d'événements à la file d'attente de threads Gui (il l'appelle depuis le thread Gui), pour simplifier les choses. – Groo

+0

Hey, vous plaisantez avec "ne sais pas la spécification exacte de Windows"? :) – Groo

1

Voir Thread Synchronization (C# Programming Guide):

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

    public void Function() 
    { 

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

Modifier: Vous ne voulez pas deux fils entrant Populate, de sorte que vous pouvez faire quelque chose comme ci-dessous:

public void Populate(bool reload) 
{ 

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

     // do actual work. 
    } 

} 

Edit2: Vous avez obtenu une bonne chose allant avec BackgroundWorker, alors peut-être que vous pourriez faire quelque chose comme ça pour laisser l'autre thread attendre.

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

Dans le contexte du code ci-dessus, où dois-je l'utiliser? – GenericTypeTea

Questions connexes