Поток C# не будет спать?


у меня есть этот код :

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();
}

Я хотел посмотреть, что произойдет, если интервал составляет 1000 мс, а процесс задания-3000 МС.

однако я видел странное поведение - задержка 3000 МС происходит только в начале !

Как я могу сделать каждый doWork сон 3000 МС?

Как вы можете видеть здесь, в начале есть 3-секундная задержка, а затем он повторяет 1 секунду каждый.

6 54

6 ответов:

каждый раз, когда таймер тикает, вы начинаете поток, чтобы сделать некоторый сон; этот поток полностью изолирован, и таймер будет продолжать стрелять каждую секунду. На самом деле, таймер срабатывает каждую секунду даже если перемещение Sleep(3000) на c().

то, что у вас есть в настоящее время:

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)
...

непонятно, что вы пытаетесь сделать. Вы можете отключить таймер, когда вы не хотите, чтобы он стрелял, и возобновить его снова после готовности, но неясно, что цель Sleep() здесь. Другой вариант-это просто while петли с Sleep() в нем. Просто, и не включает много потоков.

каждую секунду вы начинаете новый поток с задержкой 3 сек. Это происходит так:

  1. поток 1 старт
  2. запустить поток 2, поток 1 проживающих
  3. поток 3 пуск, поток 2 спит, поток 1 спит
  4. резьба 4 пуск, резьба 3 спальных мест, 2 спальных места резьбы, резьба 1 проживающих
  5. резьба 5 пуск, резьба 4 спальных места, 3 спальных места резьбы, резьба 2 спальных места, 1 нить дампы
  6. резьба 6 начало, нить, 5 спальных мест, 4 спальных места резьбы, резьба 3 спальных мест, 2-нить сваливает
  7. нить 7 Пуск, резьба 6 проживающих, резьба 5 спальных мест, 4 спальных места резьбы, резьба 3 свалки

Как вы можете видеть, каждый поток спит в течение 3 секунд, но дамп происходит каждую секунду.

Как работать с потоками? что-то вроде этого:

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

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

ваш пример очень интересен-он показывает побочные эффекты параллельной обработки. Чтобы ответить на ваш вопрос и облегчить просмотр побочных эффектов, я немного изменил ваш пример:

using System;
using System.Threading;
using System.Diagnostics;

public class Program
{
    public static void Main()
    {
        (new Example()).Main();
    }
}

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

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

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

то, что я изменил здесь следующее:

  • интервальный таймер теперь 10 мс, нити еще 3000 МС. Эффект заключается в том, что в то время как потоки спят, новые потоки будут создаваться
  • я добавил varialbe t, которым подсчитывает количество активных в данный момент потоков (оно увеличивается при запуске потока и уменьшается непосредственно перед его завершением)
  • я добавил 2 оператора дампа, распечатав начало потока и конец потока
  • наконец, я дал параметр функции doWork другое имя (h2), которое позволяет увидеть значение базовой переменной h

теперь это интересно, чтобы увидеть выход этой измененной программы в LinqPad (обратите внимание, что значения не всегда совпадают, поскольку они зависят от условий гонки запущенных потоков):

    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]

Я думаю, что значения говорят сами за себя: происходит то, что каждые 10 мс запускается новый поток, в то время как другие все еще спят. Также интересно видеть, что h не всегда равен h2, особенно если больше потоков запускается, пока другие спят. Количество потоков (переменная t) через некоторое время стабилизируется, т. е. бег вокруг 190-194.

вы можете возразить, что нам нужно поставить блокировки на переменные t и h, например

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; }} 
      }

хотя это более чистый подход, он не изменил эффект, показанный в этом примере.

теперь, чтобы доказать, что каждый поток действительно спит 3000ms (= 3s), давайте добавим Stopwatch в рабочий поток 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();
    }
} 

для правильной очистки потоков, давайте отключим таймер после ReadLine как следует:

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

это позволяет вам увидеть, что происходит, если больше не запускаются потоки, после того как вы нажали 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] 

вы можете видеть, что они все прекращаются один за другим, как ожидалось, и они спали около 3s (или 3000ms).

причина, по которой вы видите это поведение, проста: вы планируете новый поток каждую секунду, а результат становится видимым через три секунды. Вы ничего не видите в течение первых четырех секунд; затем поток, который был запущен три секунды назад, сбрасывается; другой поток будет спать в течение двух секунд к тому времени, а еще один - в течение одной секунды. Следующий второй поток #2 сбрасывает; затем поток #3, #4 и так далее - вы получаете распечатку каждую секунду.

Если вы хотите смотрите распечатку каждые три секунды, вы должны планировать новый поток каждые три секунды с любой задержкой, которую вы хотели бы: начальный поток будет выводиться через три секунды плюс задержка; все последующие потоки будут срабатывать с трехсекундными интервалами.

похоже, что вы запускаете новый поток каждую секунду, что не является хорошей идеей, используйте backgroundworker, и когда событие backgroundworker завершило вызов функции C снова, таким образом, вам не понадобится таймер

каждый doWork спит в течение трех секунд, но их спящие перекрываются, потому что вы создаете потоки с интервалом в одну секунду.