2013-12-17 6 views
6

Ho un pulsante UI chiamato Load. Genera una discussione, che a sua volta genera un'attività. C'è un'attesa sull'attività e, se scade, l'attività viene annullata. Il pulsante Carica non è disattivato e l'utente può fare clic su di esso più volte. Ogni volta che viene cliccato, il compito precedente deve essere canelled.CancellationToken and CancellationTokenSource-Come si usa?

Mi sto confondendo su come dovrei usare CancellationTokenSource e CancellationToken qui. Beow è il codice ... puoi suggerire come usarlo e se l'utilizzo sottostante ha qualche problema? No Async, per favore, perché non siamo ancora arrivati.

CancellationTokenSource _source = new CancellationTokenSource(); 
     public void OnLoad() 
     { 
      //Does this cancel the previously spawned task? 
      _source.Cancel(); 
      _source.Dispose(); 
      _source = new CancellationTokenSource(); 
      var activeToken = _source.Token; 
      //Do I need to do the above all the time or is there an efficient way? 

      Task.Factory.StartNew(() => 
       { 
        var child = Task.Factory.StartNew(() => 
         { 
          Thread.Sleep(TimeSpan.FromSeconds(20)); 
          activeToken.ThrowIfCancellationRequested(); 
         }, activeToken); 

        if (!child.Wait(TimeSpan.FromSeconds(5))) 
        { 
         _source.Cancel(); 
        } 
       }); 
     } 

Nota ho bisogno di cancellare eventuali compiti precedentemente deposto le uova, e ogni compito generato dovrebbe avere un timeout.

+0

Penso che ci sia una costruito nel modo di cancellare un segno dopo un certo timeout. – CodesInChaos

+0

http://stackoverflow.com/a/16607800/34397 – SLaks

+0

@ SLaks: non funzionerà come sono su .NET 4.0 – Mike

risposta

5

Innanzitutto, se si utilizza Visual Studio 2012+ è possibile aggiungere il pacchetto Microsoft.Bcl.Async per aggiungere il supporto per async e altre funzionalità avanzate al progetto .NET 4.0.

Se si utilizza Visual Studio 2010, è possibile utilizzare il metodo di estensione WithTimeout fornito con la libreria ParallelExtensionsExtras. Il metodo avvolge l'attività originale con TaskCompletionSource e un timer che chiama SetCancelled se scade.

Il codice è here ma il metodo attuale è semplice:

/// <summary>Creates a new Task that mirrors the supplied task but that 
    /// will be canceled after the specified timeout.</summary> 
    /// <typeparam name="TResult">Specifies the type of data contained in the 
    /// task.</typeparam> 
    /// <param name="task">The task.</param> 
    /// <param name="timeout">The timeout.</param> 
    /// <returns>The new Task that may time out.</returns> 
    public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, 
                  TimeSpan timeout) 
    { 
     var result = new TaskCompletionSource<TResult>(task.AsyncState); 
     var timer = new Timer(state => 
         ((TaskCompletionSource<TResult>)state).TrySetCanceled(), 
         result, timeout, TimeSpan.FromMilliseconds(-1)); 
     task.ContinueWith(t => 
     { 
      timer.Dispose(); 
      result.TrySetFromTask(t); 
     }, TaskContinuationOptions.ExecuteSynchronously); 
     return result.Task; 
    } 

è possibile utilizzarlo a destra dopo aver creato il vostro compito:

var myTask=Task.Factory.StartNew(()=>{...}) 
      .WithTimeout(TimeSpan.FromSeconds(20)); 

In generale, è possibile creare il comportamento desiderato creando una TaskCompletionSource chiamando i suoi metodi SetResult, SetCancelled in risposta agli eventi o ai criteri che hai impostato.

0

Ci sono alcuni errori nel codice che rendono le cose confuse.

In primo luogo, si utilizza Thread.Sleep invece di Task.Delay o qualche altro metodo basato sul timer (consiglio vivamente di scriverlo personalmente se non si ha accesso a Task.Delay). Il sonno è un'attesa bloccante e non può essere condizionato sul segnalino annullamento. Il risultato è che i thread di thread thread preziosi vengono tenuti in ostaggio per più secondi, anche se l'operazione viene annullata. Ciò potrebbe comportare gli effetti delle pressioni dei pulsanti successive trattenute da quelle precedenti.

In secondo luogo, alla fine del l'attesa si sta annullando sorgente_ ma questo si riferisce alla corrente _value di fonte non il valore al momento è stato premuto il pulsante. Le pressioni precedenti dei pulsanti annulleranno gli effetti di pressione del pulsante successivi anziché i propri.

In terzo luogo, stai eliminando la sorgente del token di annullamento su un thread mentre corri per cancellarla su un altro. Sei fortunato che non ricevi eccezioni disposte sugli oggetti.

In quarto luogo, sarebbe ideale utilizzare async in questo tipo di situazione. Hai detto che eri solo su .Net 4.0, comunque.

fissaggio le prime tre cose che dovrebbe fare quello che sta succedendo più facile ragionare su:

CancellationTokenSource _prevSource = new CancellationTokenSource(); 
public void OnButtonPress() { 
    var curSource = new CancellationTokenSource(); 
    _prevSource.Cancel(); 
    _prevSource = curSource; 

    MyCustomDelay(TimeSpan.FromSeconds(5), curSource.Token).ContinueWith(t => { 
     curSource.Cancel(); 
    }, TaskContinuationOptions.OnlyOnRanToCompletion); 

    var r = MyCustomDelay(TimeSpan.FromSeconds(20), curSource.Token).ContinueWith(t => { 
     curSource.ThrowIfCancellationRequested(); 
    }, TaskContinuationOptions.OnlyOnRanToCompletion); 
    // after 5 seconds the token r's delay is conditions on is cancelled 
    // so r is cancelled, due to the continuation specifying OnlyOnRanToCompletion 
    // the ThrowIfCancellationRequested line won't be executed 
    // although if we removed the cancel-after-5-seconds bit then it would be 
} 
+1

.NET 4, non c'è Task.Delay –

+0

@Panagiotis L'ho modificato per dire di implementarne uno con un timer. Il costo del blocco ne fa valere la pena. –

2

Questo lo farà:

private CancellationTokenSource _cancelTasks; 

    // this starts your process 
    private void DoStuff() 
    { 
     _cancelTasks = new CancellationTokenSource(); 

     var task = new Task(() => { /* your actions here */ }, _cancelTasks.Token); 
     task.Start(); 

     if (!task.Wait(5000)) _cancelTasks.Cancel(); 
    }