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