54 votes

Un thread C# ne se met pas en veille ?

J'ai ce code :

void Main()
{
    System.Timers.Timer t = new System.Timers.Timer (1000);
    t.Enabled=true;
    t.Elapsed+= (sender, args) =>c();
    Console.ReadLine();

}

int h=0;
public void c()
{
    h++;
    new Thread(() => doWork(h)).Start();
}

public void doWork(int h)
{
    Thread.Sleep(3000);
    h.Dump();
}

Je voulais voir ce qui se passe si l'intervalle est de 1000 ms et le processus de travail de 3000 ms.

Cependant, j'ai constaté un comportement étrange - le délai de 3000 ms se produit seulement au début !

Comment puis-je faire en sorte que chaque doWork dormir 3000 ms ?

Comme vous pouvez le voir ici, au début il y a un délai de 3 secondes, et ensuite il y a des itérations de 1 seconde chacune.

enter image description here

65voto

Marc Gravell Points 482669

À chaque fois que la minuterie se déclenche, vous démarrez un thread pour dormir ; ce thread est complètement isolé et la minuterie va continuer à se déclencher toutes les secondes. En fait, le timer se déclenche toutes les secondes même si vous déplacez le Sleep(3000) en c() .

Ce que vous avez actuellement est :

1000 tick (start thread A)
2000 tick (start thread B)
3000 tick (start thread C)
4000 tick (start thread D, A prints line)
5000 tick (start thread E, B prints line)
6000 tick (start thread F, C prints line)
7000 tick (start thread G, D prints line)
8000 tick (start thread H, E prints line)
...

Ce que vous essayez de faire n'est pas clair. Vous pourriez désactiver la minuterie lorsque vous ne voulez pas qu'elle se déclenche, et la réactiver une fois prête, mais le but de l'option Sleep() est ici. Une autre option est juste un while boucle avec un Sleep() en elle. C'est simple, et ça n'implique pas beaucoup de fils.

15voto

Agent_L Points 1583

Chaque seconde, vous commencez un nouveau fil de discussion avec un délai de 3 secondes. Ça se passe comme ça :

  1. début du fil 1
  2. le fil 2 démarre, le fil 1 dort
  3. thread 3 start, thread 2 sleeps, thread 1 sleeps
  4. thread 4 start, thread 3 sleeps, thread 2 sleeps, thread 1 sleeps
  5. thread 5 start, thread 4 sleeps, thread 3 sleeps, thread 2 sleeps, thread 1 dumps
  6. thread 6 start, thread 5 sleeps, thread 4 sleeps, thread 3 sleeps, thread 2 dumps
  7. thread 7 start, thread 6 sleeps, thread 5 sleeps, thread 4 sleeps, thread 3 dumps

Comme vous pouvez le voir, chaque thread dort pendant 3 secondes, mais un dump se produit chaque seconde.

Comment travaille-t-on avec des fils ? Quelque chose comme ça :

void Main()
{
    new Thread(() => doWork()).Start();
    Console.ReadLine();
}

public void doWork()
{
    int h = 0;
    do
    {
        Thread.Sleep(3000);
        h.Dump();
        h++;
    }while(true);
}

9voto

Matt Points 3445

Votre exemple est très intéressant - il montre les effets secondaires du traitement parallèle. Pour répondre à votre question, et pour faciliter la visualisation des effets secondaires, j'ai légèrement modifié votre exemple :

void Main() 
{ 
    System.Timers.Timer t = new System.Timers.Timer (10); 
    t.Enabled=true; 
    t.Elapsed+= (sender, args) =>c(); 
    Console.ReadLine(); 
} 

int t=0; int h=0; 

public void c() 
{ 
    h++; new Thread(() => doWork(h)).Start(); 
} 
public void doWork(int h2) 
{ 
    try 
    {
        t++; string.Format("h={0}, h2={1}, threads={2} [start]", 
                            h, h2, t).Dump();
        Thread.Sleep(3000); 
    }
    finally {
        t--; string.Format("h={0}, h2={1}, threads={2} [end]", 
                            h, h2, t).Dump();
    }
} 

Ce que j'ai modifié ici est le suivant :

  • L'intervalle du timer est maintenant de 10 ms, les threads ont encore 3000 ms. L'effet est que pendant que les threads dorment, de nouveaux threads sont créés.
  • J'ai ajouté varialbe t qui compte le nombre de fils actuellement actifs (il est augmenté au début du fil et diminué juste avant la fin du fil)
  • J'ai ajouté 2 instructions dump, imprimant le début et la fin du fil.
  • Enfin, j'ai donné le paramètre de la fonction doWork un nom différent (h2), qui permet de voir la valeur de la variable sous-jacente h

Maintenant, il est intéressant de voir la sortie de ce programme modifié en LinqPad (notez que les valeurs ne sont pas toujours les mêmes car elles dépendent des conditions de course des threads lancés) :

    h=1, h2=1, threads=1 [start]
    h=2, h2=2, threads=2 [start]
    h=3, h2=3, threads=3 [start]
    h=4, h2=4, threads=4 [start]
    h=5, h2=5, threads=5 [start]
    ...
    h=190, h2=190, threads=190 [start]
    h=191, h2=191, threads=191 [start]
    h=192, h2=192, threads=192 [start]
    h=193, h2=193, threads=193 [start]
    h=194, h2=194, threads=194 [start]
    h=194, h2=2, threads=192 [end]
    h=194, h2=1, threads=192 [end]
    h=194, h2=3, threads=191 [end]
    h=195, h2=195, threads=192 [start]

Je pense que les valeurs parlent d'elles-mêmes : Ce qui se passe, c'est que toutes les 10 ms, un nouveau fil est lancé, alors que d'autres sont encore en sommeil. Il est également intéressant de voir que h n'est pas toujours égal à h2, surtout si plusieurs threads sont lancés pendant que d'autres dorment. Le nombre de threads (variable t) se stabilise après un certain temps, c'est-à-dire qu'il tourne autour de 190-194.

Vous pourriez argumenter que nous devons mettre des verrous sur les variables t et h, par exemple

readonly object o1 = new object(); 
int _t=0; 
int t {
       get {int tmp=0; lock(o1) { tmp=_t; } return tmp; } 
       set {lock(o1) { _t=value; }} 
      }

Bien que cette approche soit plus propre, elle n'a pas changé l'effet montré dans cet exemple.

Maintenant, afin de prouver que chaque thread dort réellement 3000ms (= 3s), ajoutons un fichier de type Stopwatch au fil de travail doWork :

public void doWork(int h2) 
{ 
    Stopwatch sw = new Stopwatch(); sw.Start();
    try 
    {
        t++; string.Format("h={0}, h2={1}, threads={2} [start]", 
                            h, h2, t).Dump();                               
        Thread.Sleep(3000);         }
    finally {
        sw.Stop(); var tim = sw.Elapsed;
        var elapsedMS = tim.Seconds*1000+tim.Milliseconds;
        t--; string.Format("h={0}, h2={1}, threads={2} [end, sleep time={3} ms] ", 
                            h, h2, t, elapsedMS).Dump();
    }
} 

Pour un nettoyage correct des threads, désactivons le timer après l'exécution de la fonction ReadLine comme suit :

    Console.ReadLine(); t.Enabled=false; 

Cela vous permet de voir ce qui se passe si plus aucun fil ne démarre, après que vous ayez appuyé sur ENTER :

    ...
    h=563, h2=559, threads=5 [end, sleep time=3105 ms] 
    h=563, h2=561, threads=4 [end, sleep time=3073 ms] 
    h=563, h2=558, threads=3 [end, sleep time=3117 ms] 
    h=563, h2=560, threads=2 [end, sleep time=3085 ms] 
    h=563, h2=562, threads=1 [end, sleep time=3054 ms] 
    h=563, h2=563, threads=0 [end, sleep time=3053 ms] 

Vous pouvez voir qu'ils sont tous terminés l'un après l'autre comme prévu et qu'ils ont dormi environ 3s (ou 3000ms).

6voto

dasblinkenlight Points 264350

La raison de ce comportement est simple : vous planifiez un nouveau thread chaque seconde, le résultat devenant visible trois secondes plus tard. Vous ne voyez rien pendant les quatre premières secondes ; ensuite, le thread qui a été lancé il y a trois secondes se vide ; un autre thread aura dormi pendant deux secondes, et un autre encore - pendant une seconde. La seconde suivante, c'est le fil n° 2 qui se vide, puis le fil n° 3, le fil n° 4, et ainsi de suite - vous obtenez une impression chaque seconde.

Si vous souhaitez voir une impression toutes les trois secondes, vous devez programmer un nouveau fil d'exécution toutes les trois secondes avec le délai que vous souhaitez : le fil d'exécution initial produira une impression en trois secondes plus le délai ; tous les fils d'exécution suivants seront lancés à intervalles de trois secondes.

2voto

JohnnBlade Points 2713

Il semble que vous exécutez un nouveau thread toutes les secondes, ce qui n'est pas une bonne idée, utilisez le backgroundworker, et lorsque l'événement backgroundworker est terminé, appelez à nouveau la fonction C, de cette façon vous n'aurez pas besoin d'un timer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X