2015-07-22 20 views
7

Non riesco a spiegarmi in termini chiari perché un compito generato da un timer funziona correttamente ma un timer generato da un'attività non lo fa.Timer generato da attività e attività generate da timer

Tutto il codice pertinente è incluso di seguito in modo da poterlo facilmente riprodurre.

Form.cs:

private void Form1_Load(object sender, EventArgs e) 
{ 
    ProcessDelayList list = new ProcessDelayList(); 

    foreach (ProcessDelay p in list) 
    { 
     //this works 
     p.Start(); 

     //this does NOT work 
     //Task.Factory.StartNew(() => p.Start()); 
    } 
} 

ProcessDelayList.cs:

public class ProcessDelayList : List<ProcessDelay> 
{ 
    public ProcessDelayList() 
    { 
     Add(new ProcessDelay("Process 1", 2000)); 
     Add(new ProcessDelay("Process 2", 4000)); 
     Add(new ProcessDelay("Process 3", 6000)); 
     Add(new ProcessDelay("Process 4", 8000)); 
     Add(new ProcessDelay("Process 5", 10000)); 
    } 
} 

ProcessDelay.cs:

public class ProcessDelay 
{ 
    private string name; 
    private int delay; 
    private Timer timer; 

    public ProcessDelay(string name, int delay) 
    { 
     this.name = name; 
     this.delay = delay; 
    } 
    public void Start() 
    { 
     timer = new Timer(); 
     timer.Interval = delay; 
     timer.Tick += timer_Tick; 
     timer.Start(); 
    } 
    private void timer_Tick(object sender, EventArgs e) 
    { 
     //these work either way, as long as the task 
     // is NOT spawn in the main loop. 

     //TimerProc(); 
     TimerProcTask(); 
    } 
    private void TimerProcTask() 
    { 
     Task.Factory.StartNew(() => TimerProc()); 
    } 
    private void TimerProc() 
    { 
     timer.Stop(); 
     MessageBox.Show(name, delay.ToString()); 
    } 
} 
+0

Puoi provare a utilizzare la nuova attività (() => p.Start()). Start() ;? – thinklarge

+0

@thinklarge: InvalidOperationException: l'avvio potrebbe non essere richiamato su un'attività già avviata. – jsanalytics

risposta

8

Ah, timer. Ce ne sono quattro in .NET, ognuno con comportamenti leggermente diversi. Stai utilizzando System.Windows.Forms.Timer.

Questo timer utilizza la coda messaggi Win32 per gli eventi timer di attivazione (WM_TIMER). Il thread che crea il timer è quello su cui viene eseguito il metodo di callback (timer_Tick). Il thread necessita di un messaggio pump per l'esecuzione del timer.

Raccontare il compito di eseguire sul SynchronizationContext corrente sarà farlo funzionare:

Task.Factory.StartNew(() => p.Start(), 
       CancellationToken.None, 
       TaskCreationOptions.LongRunning, 
       TaskScheduler.FromCurrentSynchronizationContext()); 

Questa marescialli in realtà la chiamata per accadere sul thread dell'interfaccia utente, però, così sembra sorta di inutile per me, se tutti stai facendo sta chiamando il metodo p.Start() in ogni caso (praticamente agisce su un singolo thread).

Nota sezione Osservazioni della classe System.Windows.Forms.Timer:

Questo timer Windows è progettato per un ambiente a thread singolo in cui sono utilizzati fili di interfaccia utente per eseguire l'elaborazione. Richiede che il codice utente disponga di una pompa di messaggio dell'interfaccia utente disponibile e che funzioni sempre dallo stesso thread o esegua il marshalling della chiamata su un altro thread.

È possibile utilizzare System.Threading.Timer (o la System.Timers.Timer involucro di questa classe) se si desidera che il timer chiamate per eseguire in realtà su un thread separato. Se è necessario richiamare il timer per aggiornare l'interfaccia utente, è necessario eseguire il marshalling della chiamata di aggiornamento dell'interfaccia utente al thread dell'interfaccia utente. Tuttavia, puoi fare in modo che qualsiasi lavoro che richiede un'intensità di elaborazione venga eseguito su un thread separato e solo la quantità minima di codice (ad esempio l'effettivo aggiornamento dei controlli) viene eseguita sul thread dell'interfaccia utente per mantenerlo reattivo.