2015-11-05 3 views
1

Я прочитал много ответов по этому вопросу, но пока не нашел решение.Снова при использовании блокировки

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

public class MyClass { 
    private Timer _timer; 
    private int _threadsCounter = 0; 
    public StreamWriter Tracer { get; set; } 

    public MyClass() { 
     _timer = new Timer(1000.0 * 10); 
     _timer.AutoReset = true; 
     _timer.Elapsed += new ElapsedEventHandler(OnTimer); 
     _timer.Start(); 
    } 

    private void OnTimer(object sender, ElapsedEventArgs e) { 
     HashSet<Task> taskPool = new HashSet<Task>(); 
     try { 
      if (Tracer != null) Tracer.WriteLine("[{0}] onTimer start. Current threads counter is {1}.", DateTime.Now, _threadsCounter); 
      if (_threadsCounter >= 10) return; 

      // create parallel tasks 
      for (int i = 0; i < 8; i++) { 
       // limit on the max num of parallel processing but the counter remains unchanged during this timer event!!! 
       if (_threadsCounter >= 10) break; 

       var timeout = (30 + i * 2); 
       var task = Task.Run(() => { 
         var localCounter = System.Threading.Interlocked.Increment(ref _threadsCounter); 
         try { 
          System.Threading.Thread.Sleep(timeout * 1000); 
         } 
         finally { 
          System.Threading.Interlocked.Decrement(ref _threadsCounter); 
         } 
        }); 
       taskPool.Add(task); 
      } 

     } 
     finally { 
      if (Tracer != null) 
       Tracer.WriteLine("[{0}] onTimer end. Created {1} tasks. Current threads counter is {2}.", DateTime.Now, taskPool.Count, _threadsCounter); 
     } 
    } 

Ну, кажется, что OnTimer кэширует переменную _threadsCounter как выход:

[14:10:47] onTimer start. Current threads counter is 0. 
[14:10:47] onTimer end. Created 8 tasks. Current threads counter is 0. 

[14:10:57] onTimer start. Current threads counter is 8. 
[14:10:57] onTimer end. Created 8 tasks. Current threads counter is 8. 

[14:11:07] onTimer start. Current threads counter is 16. 
[14:11:07] onTimer end. Created 0 tasks. Current threads counter is 16. 

[14:11:17] onTimer start. Current threads counter is 15. 
[14:11:17] onTimer end. Created 0 tasks. Current threads counter is 15. 

[14:11:37] onTimer start. Current threads counter is 4. 
[14:11:37] onTimer end. Created 8 tasks. Current threads counter is 4. 

[14:11:47] onTimer start. Current threads counter is 8. 
[14:11:47] onTimer end. Created 8 tasks. Current threads counter is 8. 

[14:11:57] onTimer start. Current threads counter is 16. 
[14:11:57] onTimer end. Created 0 tasks. Current threads counter is 16. 

Почему я приезжаю к 16? Я решил эту проблему, изменив чуток код:

var localCounter = _threadsCounter; 
... 
if ((localCounter + taskPool.Count) >= 10) break; 

Но почему это поведение?

+2

похоже, вы проверяете '_threadsCounter' в цикле, но фактический код, который изменяет' _threadsCounter' в вашей задача, которая разворачивается на другой поток. Поэтому, конечно, ваш '_threadsCounter' не будет увеличиваться в следующий раз через ваш цикл. Задача, которую вы начали на последней итерации, вероятно, еще не запущена. –

+1

Возможно, вам повезло с «исправлением». Действительно, если '_threadsCounter' является общим ресурсом среди нескольких потоков, вам нужен синхронизированный доступ для ВСЕХ чтения/записи на этот общий ресурс. –

+0

@ChrisO: Не повезло, 'taskPool.Count' фактически обновляется, когда задачи добавляются в пул. Другими словами, в цикле. Хотя '_threadsCounter' - нет. Он обновляется только в том случае, когда ОС приближается к фактическому началу этих потоков (что может быть после завершения цикла). –

ответ

3

Task.Run не запускает задачу немедленно. Он добавляет задачу в очередь пула потоков и возвращает.

В вашем случае цикл for выполняется до запуска новых задач, поэтому ничего не изменяется _threadsCounter. Вот почему volatile не помогает.

1

Вы эффективно проверяете количество запущенных задач и добираетесь до приращения счетчика. Это займет немного времени - поэтому в основном вы создаете все 8 задач и запускаете их, затем, они увеличивают счетчик ... к тому времени уже слишком поздно заметить, что у вас больше 10.

лучшее решение было бы увеличить счетчик перед тем запуска задачи:

// Increment the counter in expectation of starting a task 
var localCounter = Interlocked.Increment(ref _threadsCounter); 
if (localCounter >= 10) 
{ 
    // Ah, we're not going to start a task after all, so undo 
    // the increment 
    Interlocked.Decrement(ref _threadsCounter); 
    break; 
} 
else 
{ 
    // Start a task, which will decrement the counter at the end. 
    // (You could add the "decrement" bit as a continuation, even...) 
}