2017-01-10 3 views
3

J'ai plusieurs zones de texte dans mon application wpf. L'événement LostFocus de chaque zone de texte démarre un arrière-plan pour envoyer les données à un port série connecté.wpf C# backgroundworker attente jusqu'à la fin

private readonly BackgroundWorker online_mode_send_worker = new BackgroundWorker(); 
online_mode_send_worker.DoWork += online_mode_send_worker_DoWork; 
online_mode_send_worker.RunWorkerCompleted += online_mode_send_worker_RunWorkerCompleted; 

private void TextBox_LostFocus(object sender, RoutedEventArgs e) 
{ 
    online_mode_send_worker.RunWorkerAsync(data); 
} 

private void online_mode_send_worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    List<object> data = (List<object>)e.Argument; 
    Port.WriteLine(STARTCHARACTER + XMLSET + XML_TAG_START + data[0] + XML_TAG_STOP + data[1] + ENDCHARACTER); 
    string received = Port.ReadLine(); 
} 

private void online_mode_send_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    //do some things after worker completed 
} 

À ce stade, tout fonctionne correctement.

Mais parfois je dois envoyer deux points de données directement l'un après l'autre et là j'ai un problème.

private void TextBox_LostFocus(object sender, RoutedEventArgs e) 
{ 
    online_mode_send_worker.RunWorkerAsync(data1); 
    //wait until backgroundworker has finished 
    online_mode_send_worker.RunWorkerAsync(data2); 
} 

Le Backgroundworker est toujours en cours d'exécution et je reçois une exception levée. Est-il possible d'attendre après le premier online_mode_send_worker.RunWorkerAsync(data) jusqu'à ce qu'il ait fini et ensuite commencer le deuxième online_mode_send_worker.RunWorkerAsync(data)?

while(online_mode_send_worker.isBusy); ne fonctionne pas parce que le thread principal est le blocage et la RunWorkerCompleted() n'est pas jeté et donc le Backgroundwoker est toujours occupé.

J'ai trouvé quelque chose comme ceci, mais Application.DoEvents() n'est pas disponible en wpf.

while (online_mode_send_worker.IsBusy) 
{ 
    Application.DoEvents(); 
    System.Threading.Thread.Sleep(100); 
} 
+0

moins que quelque chose existe déjà je penser que vous pouvez créer un service qui encapsule le travailleur de fond. cette classe aura également une file d'attente. pour contenir des données. lorsque le travail est démarré, le travailleur traite les données. Si plus de données doivent être envoyées, ajoutez-les à la file d'attente. lorsque le travailleur l'exécute, il vérifie la file d'attente pour voir s'il y a plus de travail à faire et supprime la file d'attente du travail suivant et appelle à nouveau le travailleur d'arrière-plan sinon il termine normalement. – Nkosi

+0

Utilisez 'Tâche' au lieu de Travailleur de fond. Il semble vraiment démodé d'utiliser un Backgroundworker. https://www.dotnetperls.com/async – Mat

+0

Voir msdn: https://msdn.microsoft.com/fr-fr/library/58195swd(v=vs.110).aspx – jdweng

Répondre

2

Voici une idée approximative de ce que je l'ai mentionné dans les commentaires.

public class Messenger { 
    private readonly BackgroundWorker online_mode_send_worker = new BackgroundWorker(); 
    private readonly ConcurrentQueue<object> messages; 

    public Messenger() { 
     messages = new ConcurrentQueue<object>(); 
     online_mode_send_worker.DoWork += online_mode_send_worker_DoWork; 
     online_mode_send_worker.RunWorkerCompleted += online_mode_send_worker_RunWorkerCompleted; 
    } 

    public void SendAsync(object message) { 
     if (online_mode_send_worker.IsBusy) { 
      messages.Enqueue(message); 
     } else { 
      online_mode_send_worker.RunWorkerAsync(message); 
     } 
    } 

    public Action<object> MessageHandler = delegate { }; 

    private void online_mode_send_worker_DoWork(object sender, DoWorkEventArgs e) { 
     if (MessageHandler != null) 
      MessageHandler(e.Argument); 
    } 

    private void online_mode_send_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { 
     object nextMessage = null; 
     if (messages.Count > 0 && messages.TryDequeue(out nextMessage)) { 
      online_mode_send_worker.RunWorkerAsync(nextMessage); 
     } 
    } 

} 

Vous avez une file d'attente pour conserver les messages envoyés alors que le travailleur de fond était occupé et ont le travailleur vérifier la file d'attente pour tous les messages en attente lorsqu'il a terminé de faire son travail.

Le messager peut être utilisé comme ceci.

private Messenger messenger = new Messenger(); 

private void Initialize() { //I would expect this to be in the constructor 
    messenger.MessageHandler = MessageHandler; 
} 

private void TextBox_LostFocus(object sender, RoutedEventArgs e) 
{ 
    messenger.SendAsync(data); 
} 

private void MessageHandler(object message) 
{ 
    List<object> data = (List<object>)message; 
    Port.WriteLine(STARTCHARACTER + XMLSET + XML_TAG_START + data[0] + XML_TAG_STOP + data[1] + ENDCHARACTER); 
    string received = Port.ReadLine(); 
} 
+1

J'ai du mal à trouver un problème concret, je suis sûr que cela fonctionnera dans cette situation. Ma principale plainte serait toujours que c'est une solution non standard et légèrement trop compliquée. Encore un +1. –

0

Il semble que j'ai raté les choses en série. Donc ce que vous voulez faire est de synchroniser vos asynchronuouscalls:

private void Button_Click(object sender, RoutedEventArgs e) 
{ 
    Task.Run(() => mySerialDevice1.WriteData(data1)); 
    Task.Run(() => mySerialDevice1.WriteData(data2)); 
} 

public class SerialDevice 
{ 
    public Port Port { get; set; } 
    public object _LockWriteData = new object(); 
    public void WriteData(string data) 
    { 
     lock(_LockWriteData) 
     { 
      Port.WriteLine(data); 
     } 
    } 
} 

voir aussi:

RÉPONSE ORIGINAL

Vous pouvez utiliser Task au lieu de Backgroundworker.

private void Button_Click(object sender, RoutedEventArgs e) 
{ 
    Task.Run(() => OnlineModeSendData(data1)); 
    Task.Run(() => OnlineModeSendData(data2)); 
} 
private void OnlineModeSendData(List<string> data) 
{ 
    Port.WriteLine(STARTCHARACTER + XMLSET + XML_TAG_START + data[0]+ XML_TAG_STOP + data[1] + ENDCHARACTER); 
    string received = Port.ReadLine(); 
} 

Je voudrais également suggérer que vous fassiez des objets réels au lieu de passer des tableaux de chaînes comme arguments.

Pour exemple envoyer BlinkLedRequest:

public class BlinkLedRequest 
{ 
    public int LedId{get;set;} 
    public int DurationInMilliseconds {get;set} 
} 

et une méthode correspondante:

public void SendBlinkLed(BlickLedRequest request) 
{ 
.... 
} 
+2

Vous semblez penser qu'un port série est thread-safe un réentrant. Ce n'est pas. –

+0

merci pour cela – Mat

+0

Aussi, 'async void' comme cible pour Task.Run()? Ne fais pas ça. –

0

Je pense que votre événement devrait utiliser RunWorkerCompleted et ajouter un délégué:

 online_mode_send_worker.RunWorkerCompleted += (s, ev) => 
       { 
        if (ev.Error != null) 
        { 
         //log Exception 
        } 
        //if(conditionToBrake) 
        // return; 
        online_mode_send_worker.RunWorkerAsync(data2); 
       }; 
online_mode_send_worker.RunWorkerCompleted(data1); 

Assurez-vous que vous mettez là une condition pour éviter la boucle infinie.

0

Mise à jour:

bw.RunWorkerAsync(data1); 
//wait here 
bw.RunWorkerAsync(data2); 

est pas bon aproche, parce que l'interface utilisateur sera bloqué sur le temps d'attente. Mieux:

bw.RunWorkerAsync(new object[] { data1, data2 }); //or new object[] { data1 } if no data2 

réponse originale:

conseils Je ne pas utiliser la construction: while (bw.Busy) { ... } (il consomme du temps cpu), les objets de synchronisation d'utilisation, par exemple, ManualResetEvent

BackgroundWorker est grande classe, mais ne ne supporte pas l'attente. Il suffit de créer un objet d'addition d'attente:

var bw = new BackgroundWorker(); 
bw.DoWork += Bw_DoWork; 
bw.RunWorkerCompleted += Bw_RunWorkerCompleted; 
bool wasError; 
ManualResetEvent e = null; 

private void TextBox_LostFocus(object sender, RoutedEventArgs e) 
{ 
    if (e != null) 
     return; 
    wasError = false; 
    e = new ManualResetEvent(false); //not signaled 
    bw.RunWorkerAsync(data1); 
    e.Wait(); //much better than while(bw.Busy()) 
    if (!wasError) 
     bw.RunWorkerAsync(data2); 
    e = null; 
} 

private void Bw_DoWork(object sender, DoWorkEventArgs e) 
{ 
    //background work in another thread 
} 

private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    if (e.Error != null) 
    { 
     //catch exception here 
     wasError = true; 
    } 
    e.Set(); //switch to signaled 
} 
0

Je dirais que si vous devez attendre jusqu'à ce que le premier « travail » est fait, que ce que vous voulez est Task.ContinueWith() et de modifier l'interface en conséquence. Le msdn page is good pour cela IMO, mais faites attention que vous attendez sur l'objet de tâche "correct". Astuce: c'est la valeur de retour de ContinueWith() que vous devez appeler Wait(). C'est un bon modèle à faire pour lancer un et ensuite l'attendre plus tard tant que vous pouvez garder le Task qui est retourné pour que vous puissiez y attendre.

Pour un générique "Je ne veux qu'un thread de fond qui fait les choses dans l'ordre où ils sont ajoutés, et je veux attendre qu'ils soient TOUS terminés et je sais quand j'ai fini d'ajouter." Je suggère d'utiliser un BlockingCollection<Action> avec un seul thread les consommer. Un exemple de comment faire est trouvé in this other answer.

0

Si vous avez besoin que vous pouvez appeler deux fois le faire:

bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); 

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     online_mode_send_worker.RunWorkerAsync(data2); 
    } 

Mais si vous avez besoin de faire la queue des commandes dont vous avez besoin de réécrire une autre manière utilisant la tâche. Une tâche à l'intérieur de laquelle vous aurez une boucle forcée où vous enverrez vos données par le port série séquentiellement.

https://msdn.microsoft.com/pt-br/library/system.threading.tasks.task(v=vs.110).aspx