Работа с потоками
Передача параметров
Общение с потоком (передача параметров, возвращение результатов) можно реализовать с помощью глобальных переменных.
class Program { static long Factorial(long n) { long res = 1; do { res = res * n; } while(--n > 0); return res; } static void Main() { long res1, res2; long n1 = 5000, n2 = 10000; Thread t1 = new Thread(() => { res1 = Factorial(n1)) }); Thread t2 = new Thread(() => { res2=Factorial(n2); }); // Запускаем потоки t1.Start(); t2.Start(); // Ожидаем завершения потоков t1.Join(); t2.Join(); Console.WriteLine("Factorial of {0} equals {1}", n1, res1); Console.WriteLine("Factorial of {0} equals {1}", n2, res2); } }
Существует возможность передать параметры в рабочий метод потока с помощью перегрузки метода Start. Сигнатура рабочего метода строго фиксирована – либо без аргументов, либо только один аргумент типа object. Поэтому при необходимости передачи нескольких параметров в рабочем методе необходимо выполнить правильные преобразования.
class Program { static double res; static void ThreadWork(object state) { string sTitle = ((object[])state)[0] as string; double d = (double)(((object[])state)[1]); Console.WriteLine(sTitle); res = SomeMathOperation(d); } static void Main() { Thread thr1 = new Thread(ThreadWork); thr1.Start(new object[] {"Thread #1", 3.14}); thr1.Join(); Console.WriteLine("Result: {0}", res); } }
Работа в лямбда-выражениях и анонимных делегатах с общими переменными может приводить к непредсказуемым результатам.
for(int i=0; i<10; i++) { Thread t = new Thread(() => Console.Write("ABCDEFGHIJK"[i])) t.Start(); }
Ожидаем получить все буквы в случайном порядке, а получаем
BDDDEEJJKK
Если в строковой константе оставить только 10 букв, полагая, что индекс i может быть от 0 до 9, получаем ошибку "Индекс вышел за границы массива".
Проблема связана с тем, что при объявлении потока делегат метода или лямбда-выражение содержит только ссылку на индекс i. Когда созданный поток начинает свою работу фактическое значение индекса уже давно убежало вперед. Последнее значение индекса равно 10, что и приводит к возникновению исключения. Исправить данный фрагмент можно с помощью дополнительной переменной, которая на каждой итерации сохраняет текущее значение индекса.
for(int i=0; i<10; i++) { int i_copy = i; Thread t = new Thread(new delegate( Console.Write("ABCDEFGHIJK"[i_copy])) t.Start(); }
Теперь у каждого потока своя независимая переменная i_copy c уникальным значением. В результате получаем:
ABCDEFGHIJK
или в произвольном порядке, но все буквы по одному разу
BDACFGHEIJK
Приостановление потока
Метод Sleep() позволяет приостановить выполнение текущего потока на заданное число миллисекунд:
// Приостанавливаем поток на 100 мс Thread.Sleep(100); // Приостанавливаем поток на 5 мин Thread.Sleep(TimeSpan.FromMinute(5));
Если в качестве аргумента указывается ноль Thread.Sleep(0), то выполняющийся поток отдает выделенный квант времени и без ожидания включается в конкуренцию за процессорное время. Такой прием может быть полезен в отладочных целях для обеспечения параллельности выполнения определенных фрагментов кода.
Например, следующий фрагмент
static void ThreadFunc(object o) { for(int i=0; i<20; i++) Console.Write(o); } static void Main() { Thread[] t = new Thread[4]; for(int i=0; i<4; i++) t[i] = new Thread(ThreadFunc); t[0].Start("A"); t[1].Start("B"); t[2].Start("C"); t[3].Start("D"); for(int i=0; i<4; i++) t[i].Join(); }
BBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDD
Параллельность не наблюдается, так как каждый поток за выделенный квант процессорного времени успевает обработать все 20 итераций. Изменим тело цикла рабочей функции:
static void ThreadFunc(object o) { for(int i=0; i<20; i++) { Console.Write(o); Thread.Sleep(0); } }
Вывод стал более разнообразный:
AAAACACACACACACACACACACACACACACACAACCBBDDDBDBDBDBDBDBDBDBDBDBDBDBDBCCCDBBBDDBBDD
Существует аналог метода Thread.Sleep(0), который позволяет вернуть выделенный квант – Thread.Yield(). При этом возврат осуществляется только в том случае, если для ядра, на котором выполняется данный поток, есть другой готовый к выполнению поток. Неосторожное применение методов Thread.Sleep(0) и Thread.Yield() может привести к ухудшению быстродействия из-за не оптимального использования кэш-памяти.