2014-06-30 7 views
0

Mon DoWork pour backgroundworker1 définit un délai d'attente via une classe WakeUp, cela fonctionne bien.Annulation d'un BackgroundWorker

Le problème actuel est que parfois mon while CancellationPending se termine par une boucle infinie. Donc, à l'intérieur de DoWork il y a un appel à WaitOne, le DoWork définit la minuterie d'attente et attend le thread jusqu'à ce que le minuteur se déclenche.

J'ai besoin pour le BackgroundWorker de fermer tout de suite, mais aussi je dois garder une référence à BackgroundWorker afin que je puisse garder une trace de chaque alarme dans mon programme. Pourquoi CancellationPending prend tellement de temps? Ça ne semble jamais finir. Y at-il un moyen de tuer le backgroundworker sans avoir à attendre comme ça dans une boucle pendant si longtemps?

switch (alarmNum) 
{ 
    case 1: 
       WakeUp.CancelWakeUp(threadHandles[removal]); 
     if(backgroundworker1.IsBusy) 
     { 

      backgroundworker1.CancelAsync(); 
     } 

     while(backgroundworker1.CancellationPending) 
     { 

     } 

     backgroundworker1.RunWorkerAsync(); 
     break; 
    case 2: 
     if (backgroundworker2.IsBusy) 
     { 
      backgroundworker2.CancelAsync(); 
     } 
     backgroundworker2.RunWorkerAsync(); 
     break; 
    case 3: 
     if (backgroundworker3.IsBusy) 
     { 
      backgroundworker3.CancelAsync(); 
     } 
     backgroundworker3.RunWorkerAsync(); 
     break; 
    case 4: 
     if (backgroundworker4.IsBusy) 
     { 
      backgroundworker4.CancelAsync(); 
     } 
     backgroundworker4.RunWorkerAsync(); 
     break; 
    case 5: 
     if (backgroundworker5.IsBusy) 
     { 
      backgroundworker5.CancelAsync(); 
     } 
     backgroundworker5.RunWorkerAsync(); 
     break; 
} 



    private void bw1_DoWork(object sender, DoWorkEventArgs e) 
    { 
     BackgroundWorker worker = sender as BackgroundWorker; 


     if (worker.CancellationPending == true) 
     { 
      e.Cancel = true; 
     } 

     WakeUp temp = new WakeUp("spalarm1"); 
     threadHandles[0] = temp.tHandle; 
     temp.initWakeUp(dtCurSpan); 

     //**************************** 
       It's blocking here -< <-- 
       temp.DoWork sets a system timer so its blocking 
       with a call to WaitOne inside WakeUp. The timer I'm setting 
       is called a waitable timer so its blocking since the system is 
       waiting for it to expire so the thread can end 
      *******************************/ 
     temp.DoWork(); 
    } 

WakeUp.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

using System.Windows.Controls; 
using System.Threading.Tasks; 
using System.Threading; 
using System.Runtime.InteropServices; 
using Microsoft.Win32.SafeHandles; 
using System.ComponentModel; 
using System.Diagnostics; 

namespace WpfApplication1 
{ 
    class WakeUp 
    { 
     public delegate void TimerCompleteDelegate(IntPtr complretionArg, 
               UInt32 timerLow, UInt32 timerHigh); 

     public SafeWaitHandle tHandle; 
     bool rslt; 

     //Various imports of kernel32.dll so the waitable timer can be set 
     //on the system 
     [DllImport("kernel32.dll")] 
     public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName); 

     [DllImport("kernel32.dll", SetLastError = true)] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, [In] ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume); 

     [DllImport("kernel32.dll", SetLastError = true)] 
     public static extern bool CancelWaitableTimer(SafeWaitHandle hTimer); 

     //SafeHandle.DangerousGetHandle Method 
     [DllImport("kernel32.dll", SetLastError = true)] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     public static extern bool CloseHandle(IntPtr hObject); 

     //The constructor will use a TimeSpan to set a waitable timer 
     public WakeUp(string wtName) 
     { 
      tHandle = CreateWaitableTimer(IntPtr.Zero, true, wtName); 
     } 

     public int initWakeUp(TimeSpan smParam) 
     { 
      //The number of ticks needs to be negated to set a waitable timer in this fashion 
      long waketicks = -smParam.Ticks; 
      rslt = SetWaitableTimer(tHandle, ref waketicks, 0, IntPtr.Zero, IntPtr.Zero, true); 

      if(!rslt) 
      { 
       return Marshal.GetLastWin32Error(); 
      } 
      else 
      { 
       return 0; 
      } 
     } 

     private static Exception GetWin32Exception() 
     { 
      return Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); 
     } 

     public int DoWork() 
     { 
      using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset)) 
      { 
       wh.SafeWaitHandle = tHandle; 
       wh.WaitOne(); 
       Thread.Sleep(5000); 
      } 

      return 0; 
     } 



     //This function needs to check the return value of 
     //CancelWaitableTimer 
     static public void CancelWakeUp(SafeWaitHandle clHandle) 
     { 
      CancelWaitableTimer(clHandle); 
     } 
    } 
} 

Solution possible:

case 1: 
WakeUp.CancelWakeUp(threadHandles[removal]); 

threadHandles[removal] = null; 
backgroundworker1.CancelAsync(); 
backgroundworker1.Dispose(); 

backgroundworker1 = new BackgroundWorker(); 
backgroundworker1.WorkerReportsProgress = true; 
backgroundworker1.WorkerSupportsCancellation = true; 
backgroundworker1.DoWork += new DoWorkEventHandler(bw1_DoWork); 
backgroundworker1.ProgressChanged += new ProgressChangedEventHandler(bw1_ProgressChanged); 
backgroundworker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw1_RunWorkerCompleted); 

backgroundworker1.RunWorkerAsync(); 
break; 

mais je tentais d'éviter ce type de code.

+0

Afficher le code où BackgroundWorker est annulé. Si vous voulez que BackgroundWorker annule rapidement, vérifiez rapidement. – Paparazzi

+0

Comment suis-je censé vérifier quand DoWork bloque? – Giuseppe

+0

Vous appelez la minuterie que les appels annule le travailleur d'arrière-plan dans le travailleur d'arrière-plan d'une manière bloquante, puis appelle un autre dowork? – Paparazzi

Répondre

1

Je ne suis pas familier avec les API kernel32 que vous invoquez P, donc je vais vous donner un exemple de base en utilisant System.Threading.Timer (qui peut être réinitialisé après un rappel a été planifié).

EDIT

logique timer Changé pour supprimer le rappel de la minuterie sur l'annulation afin d'apporter l'exemple plus proche de votre scénario.

using System; 
using System.ComponentModel; 
using System.Threading; 

void BackgroundWorkerTimerCancellation() 
{ 
    var worker = new BackgroundWorker { WorkerSupportsCancellation = true }; 

    // Cancellation support. 
    var cts = new CancellationTokenSource(); 
    var cancellationToken = cts.Token; 

    // Cancel worker when the CancellationTokenSource is canceled. 
    cancellationToken.Register(worker.CancelAsync); 

    // This ManualResetEvent will allow us 
    // to block until DoWork has finished 
    // (testing purposes only). 
    using (var outerMRE = new ManualResetEvent(false)) 
    { 
     worker.DoWork += delegate 
     { 
      try 
      { 
       // Mimics your EventWaitHandle. 
       using (var innerMRE = new ManualResetEvent(false)) 
       { 
        // Our timer which takes a looong time. 
        using (var timer = new Timer(_ => innerMRE.Set(), null, 10000 /* 10 seconds */, Timeout.Infinite)) 
        { 
         // Wire cancellation. 
         cancellationToken.Register(() => 
         { 
          // Cancel the timer (callback will never execute). 
          timer.Change(Timeout.Infinite, Timeout.Infinite); 

          // Signal wait handle immediately when canceled. 
          // It's lack of this call which is making 
          // your BackgroundWorker run indefinitely 
          // when the timer is canceled. 
          innerMRE.Set(); 
         }); 

         // Block until timer callback runs or until 
         // the CancellationTokenSource is canceled. 
         innerMRE.WaitOne(); 
        } 
       } 
      } 
      finally 
      { 
       // Allow the outerMRE.WaitOne() call to complete. 
       outerMRE.Set(); 
      } 
     }; 

     // Start the worker (non-blocking). 
     worker.RunWorkerAsync(); 

     // Schedule auto-cancellation after 1 second (non-blocking). 
     cts.CancelAfter(TimeSpan.FromSeconds(1)); 

     // Block until DoWork has finished. 
     // Will take 10 seconds without cancellation, 
     // or one second with cancellation. 
     outerMRE.WaitOne(); 
    } 
} 
+0

Merci. Cela peut me prendre un peu pour digérer ce que vous me montrez. – Giuseppe

+0

L'essentiel est que vous avez besoin d'un moyen d'appeler 'worker.CancelAsync' et aussi' WakeUp.CancelWakeUp' (et éventuellement de signaler votre 'tHandle') * ensemble, en même temps * pour que l'annulation se produise correctement là et ensuite. J'accomplis cela en enregistrant des rappels d'annulation sur le 'CancellationToken' (et en annulant le' CancellationTokenSource' plutôt que simplement le 'BackgroundWorker'), mais cela ne signifie pas que vous deviez - signaler que le handle d'attente est également une option faisable, mais vous devrez trouver un moyen de le faire circuler. –

+0

@ user2209542, Jouez avec ceci et éditez la question avec la dernière version du code si vous rencontrez des difficultés pour intégrer l'un des concepts dont j'ai parlé. Je voudrais vraiment voir cela (et éventuellement proposer une meilleure solution globale parce que l'utilisation des poignées d'attente dans .NET 4.5 n'est pas juste *). –