2016-05-02 36 views
7

Ho un bisogno di tenere traccia di un compito e potenzialmente in coda un'altra operazione dopo un certo ritardo in modo che il modo in cui sto pensando di farlo simile a questa:compiti concatenamento con ritardi

private Task lastTask; 

public void DoSomeTask() 
{ 
    if (lastTask == null) 
    { 
     lastTask = Task.FromResult(false); 
    } 
    lastTask = lastTask.ContinueWith(t => 
    { 
     // do some task 
    }).ContinueWith(t => Task.Delay(250).Wait()); 
} 

La mia domanda è, se faccio qualcosa di simile, la creazione di catene di attività potenzialmente lunghe è che le attività più vecchie saranno disposte o finiranno per rimanere invariate perché il ContinueWith prende l'ultima attività come parametro (quindi è una chiusura). In tal caso, come posso concatenare le attività evitando questo problema?

C'è un modo migliore per farlo?

+0

Si può supporre che 'DoSomeTask()' non verrà chiamato in parallelo? –

+0

@YacoubMassad Non influenzerebbe le domande specifiche a cui sta chiedendo, anche se lo fosse. – Servy

+0

Qualche possibilità che tu possa usare 'async' /' await'? Il codice sarà molto semplice e più facile da ragionare. –

risposta

1
Task.Delay(250).Wait() 

È sai si sta facendo qualcosa di sbagliato quando si utilizza Wait nel codice che stai cercando di fare asincrona. Questo è un thread sprecato che non fa nulla.

Di seguito sarebbe molto meglio:

lastTask = lastTask.ContinueWith(t => 
{ 
    // do some task 
}).ContinueWith(t => Task.Delay(250)).Unwrap(); 

ContinueWith restituisce una Task<Task>, e la chiamata si trasforma Unwrap che in un Task che completerà quando il compito interna fa.

Ora, per rispondere alla tua domanda, diamo un'occhiata a ciò che il compilatore genera:

public void DoSomeTask() 
{ 
    if (this.lastTask == null) 
     this.lastTask = (Task) Task.FromResult<bool>(false); 
    // ISSUE: method pointer 
    // ISSUE: method pointer 
    this.lastTask = this.lastTask 
    .ContinueWith(
     Program.<>c.<>9__2_0 
     ?? (Program.<>c.<>9__2_0 = new Action<Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_0)))) 
    .ContinueWith<Task>(
     Program.<>c.<>9__2_1 
     ?? (Program.<>c.<>9__2_1 = new Func<Task, Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_1)))) 
    .Unwrap(); 
} 

[CompilerGenerated] 
[Serializable] 
private sealed class <>c 
{ 
    public static readonly Program.<>c <>9; 
    public static Action<Task> <>9__2_0; 
    public static Func<Task, Task> <>9__2_1; 

    static <>c() 
    { 
     Program.<>c.<>9 = new Program.<>c(); 
    } 

    public <>c() 
    { 
     base.\u002Ector(); 
    } 

    internal void <DoSomeTask>b__2_0(Task t) 
    { 
    } 

    internal Task <DoSomeTask>b__2_1(Task t) 
    { 
     return Task.Delay(250); 
    } 
} 

Questo è stato decompilato con dotPeek in "mi mostrano tutte le budella" modalità.

Guardate questa parte:

.ContinueWith<Task>(
    Program.<>c.<>9__2_1 
    ?? (Program.<>c.<>9__2_1 = new Func<Task, Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_1)))) 

La funzione ContinueWith viene dato un delegato Singleton. Quindi, non c'è chiusura su nessuna variabile lì.

Ora, c'è questa funzione:

internal Task <DoSomeTask>b__2_1(Task t) 
{ 
    return Task.Delay(250); 
} 

Il t qui è un riferimento al compito precedente. Nota qualcosa? Non è mai usato La JIT contrassegnerà questo locale come irraggiungibile e il GC sarà in grado di pulirlo. Con le ottimizzazioni attivate, la JIT contrassegnerà in modo aggressivo i locali idonei per la raccolta, anche al punto che un metodo di istanza può essere eseguito mentre l'istanza viene raccolta dal GC, se il metodo di istanza non fa riferimento a this nel codice rimasto da eseguire.

Ora, un'ultima cosa, c'è il campo m_parent nella classe Task, che non va bene per il tuo scenario. Ma finché non stai usando TaskCreationOptions.AttachedToParent dovresti stare bene. È sempre possibile aggiungere il flag DenyChildAttach per maggiore sicurezza e autodocumentazione.

Ecco il function which deals with that:

internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptions) 
{ 
    return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null; 
} 

Quindi, si dovrebbe essere al sicuro qui. Se si desidera essere sicuro, eseguire un profiler di memoria su una lunga catena e vedere di persona.

2

se faccio qualcosa di simile, creando potenzialmente lunghe catene di attività è la volontà dei compiti più anziani essere smaltiti

Compiti non richiedono disposizione esplicita, in quanto non contengono risorse non gestite.

finiranno attaccare intorno per sempre perché il ContinueWith prende l'ultimo compito come parametro (quindi è una chiusura)

E 'Non una chiusura. Una chiusura è un metodo anonimo che utilizza una variabile dall'esterno del metodo anonimo nel suo corpo. Non lo stai facendo, quindi non lo stai chiudendo. Ciascuno Task dispone tuttavia di un campo in cui tiene traccia dei propri genitori, pertanto l'oggetto gestito Task sarà comunque accessibile se si utilizza questo modello.

+1

Il genitore dovrebbe essere nullo nel suo scenario, a patto che eviti 'TaskCreationOptions.AttachedToParent'. –

2

Dai uno sguardo allo source code of the ContinuationTaskFromTask class. Essa ha il seguente codice:

internal override void InnerInvoke() 
{ 
    // Get and null out the antecedent. This is crucial to avoid a memory 
    // leak with long chains of continuations. 
    var antecedent = m_antecedent; 
    Contract.Assert(antecedent != null, 
     "No antecedent was set for the ContinuationTaskFromTask."); 
    m_antecedent = null; 

m_antecedent è il campo che contiene un riferimento al l'antecedente chiedere. Gli sviluppatori qui lo impostano esplicitamente su null (dopo che non è più necessario) per assicurarsi che non ci siano perdite di memoria con lunghe catene di continuazioni, il che credo sia la vostra preoccupazione.