2015-07-22 15 views
7

Nie mogę wyjaśnić sobie w jasny sposób, dlaczego zadanie odradzane przez zegar działa dobrze, ale czas odrodzenia przez zadanie NIE ma miejsca.Timer odradza się według zadania i zadania odradza się za pomocą timera

Cały odpowiedni kod znajduje się poniżej, dzięki czemu można z łatwością go odtworzyć.

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

Czy możesz spróbować użyć nowego zadania (() => p.Start()). Start() ;? – thinklarge

+0

@thinklarge: InvalidOperationException: Start może nie zostać wywołany dla zadania, które zostało już uruchomione. – jsanalytics

Odpowiedz

8

Ach, liczniki. Istnieją cztery z nich w .NET, każdy z nieco odmiennymi zachowaniami. Używasz System.Windows.Forms.Timer.

Ten zegar wykorzystuje kolejkę komunikatów Win32 do uruchamiania zdarzeń licznika czasu (WM_TIMER). Wątek, który tworzy licznik czasu, jest tym, w którym wykonywana jest metoda wywołania zwrotnego (timer_Tick). Wątek wymaga pompy komunikatów, aby timer mógł wykonać zadanie.

powiem za zadanie działać na obecnym SynchronizationContext uczyni to działa:

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

To rzeczywiście marszałków połączenia się dziać na wątku UI, choć, więc wydaje się, jakby bez sensu do mnie, jeśli wszystko tak czy inaczej wywołujecie metodę p.Start() (prawie działa pojedynczo).

Uwaga sekcja Uwagi klasy System.Windows.Forms.Timer:

Zegar ten Windows jest przeznaczony dla jednowątkowym środowisku, w którym wątki UI są wykorzystywane do wykonywania przetwarzania. Wymaga to, aby kod użytkownika posiadał dostępną pompę komunikatów UI i zawsze działał z tego samego wątku lub przerzucił wywołanie na inny wątek.

Można użyć System.Threading.Timer (lub System.Timers.Timer owinięcie z tej klasy), jeśli chcesz nazywa twój zegar faktycznie wykonać w osobnym wątku. Jeśli potrzebujesz oddzwaniacza odliczającego czas, aby zaktualizować interfejs użytkownika, musisz zgłosić wywołanie aktualizacji interfejsu użytkownika do wątku interfejsu użytkownika. Można jednak upewnić się, że każda operacja wymagająca dużego nakładu pracy jest wykonywana w oddzielnym wątku, a tylko najmniejsza ilość kodu (na przykład faktyczne aktualizowanie elementów sterujących) jest wykonywana w wątku interfejsu użytkownika, aby zachować czas reakcji.