NET определяет многопоточность в пространстве имен System.Threading. Поэтому, чтобы использовать многопоточность, необходимо сначала объявить ссылку на это пространство имен (с помощью System.Threading;).
а. Запустить поток. Как следует из названия, «запустить поток» означает создание и запуск потока. Это можно сделать с помощью следующего кода:
Поток thread1 = новый поток (новый ThreadStart (Count));
Где Count — это функция, которая будет выполняться новым потоком.
б. Забить ветку.
«Уничтожение потока» означает уничтожение потока. Чтобы не тратить свои усилия, лучше всего определить, жив ли он еще (через атрибут IsAlive), прежде чем уничтожать поток, а затем вызвать метод Abort, чтобы убить поток. .
в) Приостановка потока означает приостановку работающего потока на определенный период времени. Например, thread.Sleep(1000); позволяет потоку заснуть на 1 секунду.
d. Приоритет не требует пояснений. Атрибут hreadPriority в классе Thread используется для установки приоритета, но нет никакой гарантии, что операционная система примет этот приоритет. Приоритет потока можно разделить на 5 типов: «Нормальный», «Выше нормального», «Ниже нормального», «Высокий» и «Низкий». Конкретные примеры реализации следующие:
поток.Приоритет = ThreadPriority.Highest;
е. Приостановить нить
Метод Suspend класса Thread используется для приостановки потока до тех пор, пока не будет вызвано Resume, прежде чем поток сможет продолжить выполнение. Если поток уже приостановлен, это не сработает.
если (thread.ThreadState = ThreadState.Running)
{
поток.Приостановить();
}
f. Поток восстановления используется для возобновления приостановленного потока, чтобы он мог продолжить выполнение. Если поток не приостановлен, он не будет работать.
если (thread.ThreadState = ThreadState.Suspended)
{
поток.Резюме();
}
Ниже приведен пример, иллюстрирующий простую функциональность потоков. Этот пример взят из справочной документации.
использование системы;
использование System.Threading;
// Простой сценарий обработки потоков: запуск статического метода
// во втором потоке.
общественный класс ThreadExample {
// Метод ThreadProc вызывается при запуске потока.
// Он повторяется десять раз, записывает в консоль и возвращает
// каждый раз остаток времени, а затем заканчивается.
общественная статическая сила ThreadProc() {
для (int я = 0; я <10; я++) {
Console.WriteLine("ThreadProc: {0}", i);
// Выдаем оставшуюся часть интервала времени.
Thread.Sleep(0);
}
}
общественная статическая сила Main() {
Console.WriteLine("Основной поток: запустить второй поток.");
// Конструктор класса Thread требует ThreadStart
// делегат, который представляет метод, который будет выполнен в
// thread. C# упрощает создание этого делегата.
Поток t = новый поток (новый ThreadStart (ThreadProc));
// Запускаем ThreadProc На однопроцессоре поток не попадает.
// любое время процессора до завершения основного потока. Раскомментируйте.
// Thread.Sleep, следующий за t.Start(), чтобы увидеть разницу.
т.Старт();
//Thread.Sleep(0);
для (int я = 0; я <4; я++) {
Console.WriteLine("Основная тема: поработайте.");
Thread.Sleep(0);
}
Console.WriteLine("Основной поток: вызовите метод Join(), чтобы дождаться завершения ThreadProc.");
т.Присоединиться();
Console.WriteLine("Основной поток: ThreadProc.Join вернулся. Нажмите Enter, чтобы завершить программу.");
Консоль.ReadLine();
}
}
Этот код выдает вывод, аналогичный следующему:
Основная ветка: запустите вторую ветку.
Основная тема: Поработайте.
Процесс обработки потока: 0
Основная тема: Поработайте.
Процесс обработки потока: 1
Основная тема: Поработайте.
Процесс обработки потока: 2
Основная тема: Поработайте.
Процесс обработки потока: 3
Основной поток: вызовите функцию Join(), чтобы дождаться завершения работы ThreadProc.
Процесс обработки потока: 4
Процесс обработки потока: 5
Процесс обработки потока: 6
Процесс обработки потока: 7
Процесс обработки потока: 8
Процесс обработки потока: 9
Основной поток: ThreadProc.Join вернулся. Нажмите Enter, чтобы завершить программу.
В Visul C# пространство имен System.Threading предоставляет некоторые классы и интерфейсы, обеспечивающие многопоточное программирование. Существует три метода создания потоков: Thread, ThreadPool и Timer. Вот краткое введение в то, как использовать их один за другим.
1. Нить
Это, пожалуй, самый сложный метод, но он обеспечивает разнообразные гибкие возможности управления потоками. Во-первых, вы должны использовать его конструктор для создания экземпляра потока.Его параметры относительно просты и содержат только один делегат ThreadStart: public Thread(ThreadStart start); затем вызовите Start(), чтобы запустить его. Конечно, вы можете использовать его атрибут Priority. чтобы установить или получить приоритет работы (перечисление ThreadPriority: Normal, Lowest, Highest, BelowNormal, UpperNormal).
В следующем примере сначала создаются два экземпляра потока t1 и t2, затем устанавливаются их приоритеты соответственно, а затем запускаются два потока (два потока по сути одинаковы, за исключением того, что их выходные данные различны: t1 — «1», а t2 — «2»). ", По соотношению количества выводимых ими символов мы можем примерно увидеть соотношение занимаемого ими процессорного времени, что также отражает их соответствующие приоритеты).
static void Main(string[] args)
{
Поток t1 = новый поток (новый ThreadStart (Thread1));
Поток t2 = новый поток (новый ThreadStart (Thread2));
t1.Priority = ThreadPriority.BelowNormal;
t2.Priority = ThreadPriority.Lowest;
т1.Старт();
т2.Старт();
}
публичная статическая пустота Thread1()
{
для (int я = 1; я <1000; я++)
{//Записывайте «1» каждый раз при выполнении цикла.
Дост();
Console.Write("1");
}
}
публичная статическая пустота Thread2()
{
для (int я = 0; я <1000; я++)
{//Записывайте цифру "2" каждый раз при выполнении цикла.
Дост();
Console.Write("2");
}
}
публичная статическая пустота dosth()
{//Используется для моделирования сложных операций.
for (int j = 0; j < 10000000; j++)
{
интервал а=15;
а = а*а*а*а;
}
}
Результатом запуска вышеуказанной программы является:
11111111111111111111111111111111111111111112111111111111111111111111111111111111111111112
11111111111111111111111111111111111111111112111111111111111111111111111111111111111111112
11111111111111111111111111111111111111111112111111111111111111111111111111111111111111112
Из приведенных выше результатов мы видим, что поток t1 занимает гораздо больше процессорного времени, чем поток t2. Это связано с тем, что приоритет t1 выше, чем у t2. Если мы установим приоритеты t1 и t2 на Normal, результаты будут. следующие изображения:
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
Из приведенного выше примера мы видим, что его структура аналогична рабочему потоку Win32, но она проще. Вам нужно только использовать функцию, вызываемую потоком, в качестве делегата, а затем использовать делегат в качестве параметра. создать экземпляр потока. Когда для запуска вызывается Start(), будет вызвана соответствующая функция, и выполнение начнется с первой строки этой функции.
Далее мы объединяем свойство потока ThreadState, чтобы понять управление потоком. ThreadState — это тип перечисления, отражающий состояние потока. Когда экземпляр Thread создается впервые, его состояние ThreadState равно Unstarted; когда поток запускается вызовом Start(), его состояние ThreadState равно Running; если вы хотите, чтобы он приостановился (заблокировался), вы можете вызвать Thread. Sleep() имеет два перегруженных метода (Sleep(int), Sleep(Timespan)), которые представляют собой разные форматы представления количества времени. Когда эта функция вызывается в потоке, это означает, что поток заблокируется. в течение определенного периода времени (время определяется количеством миллисекунд или интервалом времени, переданным в режим сна, но если параметр равен 0, это означает приостановку этого потока, чтобы позволить другим потокам выполняться, с указанием Infinite для блокировки потока на неопределенный срок), при на этот раз его ThreadState станет WaitSleepJoin. Еще стоит отметить, что функция Sleep() определена как статическая. ! Это также означает, что его нельзя использовать совместно с экземпляром потока, то есть нет вызова, подобного t1.Sleep(10)! Точно так же функция Sleep() может быть вызвана только тем потоком, который сам должен «спать», и не может быть вызвана другими потоками. Точно так же, как когда переходить в режим сна, это личное дело, которое не может быть решено другими. . Но когда поток находится в состоянии WaitSleepJoin и должен его разбудить, вы можете использовать метод Thread.Interrupt, который выдаст исключение ThreadInterruptedException в потоке. Давайте сначала рассмотрим пример (обратите внимание на вызывающий метод Sleep):
static void Main(string[] args)
{
Поток t1 = новый поток (новый ThreadStart (Thread1));
т1.Старт();
t1.Прерывание();
Е.ОжиданиеОдин();
t1.Прерывание();
t1.Присоединиться();
Console.WriteLine("t1 — конец");
}
статический AutoResetEvent E = новый AutoResetEvent(false);
публичная статическая пустота Thread1()
{
пытаться
{//Из параметров видно, что это вызовет гибернацию
Thread.Sleep(Timeout.Infinite);
}
catch(System.Threading.ThreadInterruptedException e)
{//Обработчик прерываний
Console.WriteLine("1-е прерывание");
}
Э.Установить();
пытаться
{// спать
Thread.Sleep(Timeout.Infinite);
}
catch(System.Threading.ThreadInterruptedException e)
{
Console.WriteLine("2-е прерывание");
}//Пауза на 10 секунд
Thread.Sleep (10000);
}
Результат работы: 1-е прерывание
2-е прерывание
(Через 10 с) t1 — конец
Из приведенного выше примера мы видим, что метод Thread.Interrupt может вывести программу из состояния блокировки (WaitSleepJoin) и войти в соответствующий обработчик прерывания, а затем продолжить выполнение (его состояние ThreadState также меняется на Running). функция должна учитывать следующие моменты:
1. Этот метод может не только пробудить блокировку, вызванную спящим режимом, но также эффективен для всех методов, которые могут привести к переходу потока в состояние WaitSleepJoin (например, Wait и Join). Как показано в приведенном выше примере, при его использовании следует поместить метод, вызывающий блокировку потока, в блок try, а соответствующий обработчик прерывания — в блок catch.
2. Вызовите прерывание в определенном потоке. Если он находится в состоянии WaitSleepJoin, он перейдет в соответствующий обработчик прерывания для выполнения. Если в данный момент он не находится в состоянии WaitSleepJoin, он будет немедленно прерван, когда он войдет в это состояние позже. . Если Interrupt вызывается несколько раз перед прерыванием, действительным является только первый вызов. Именно поэтому я использовал синхронизацию в приведенном выше примере, чтобы гарантировать, что второй вызов Interrupt будет вызван после первого прерывания, в противном случае это может вызвать второе. Вызов не имеет никакого эффекта (если он вызывается до первого прерывания). Вы можете попробовать удалить синхронизацию. Результат, скорее всего, будет: 1-е прерывание.
В приведенном выше примере также используются два других метода для перевода потока в состояние WaitSleepJoin: использование объекта синхронизации и метода Thread.Join. Использование метода Join относительно просто. Это означает, что текущий поток, вызывающий этот метод, блокируется до тех пор, пока другой поток (t1 в этом примере) не завершится или не пройдет указанное время (если он также принимает параметр времени). условие (если таковое имеется) немедленно завершает состояние WaitSleepJoin и переходит в состояние Running (условие можно определить на основе возвращаемого значения метода .Join. Если оно истинно, поток завершается; если ложно, время вышло). Поток также можно приостановить с помощью метода Thread.Suspend. Когда поток находится в состоянии «Выполняется» и для него вызывается метод Suspend, он перейдет в состояние SuspendRequested, но не будет приостановлен немедленно, пока поток не достигнет безопасного состояния. В этот момент поток зависает и переходит в состояние Suspended. Если вызвать поток, который уже приостановлен, он будет недействителен. Чтобы возобновить работу, просто вызовите Thread.Resume.
Последнее, о чем мы говорим, — это уничтожение потоков. Мы можем вызвать метод Abort для потока, который необходимо уничтожить, и он выдаст исключение ThreadAbortException для этого потока. Мы можем поместить некоторый код из потока в блок try и поместить соответствующий код обработки в соответствующий блок catch. Когда поток выполняет код в блоке try, если вызывается Abort, он переходит в соответствующий блок catch. Выполненный внутри блока catch, он завершится после выполнения кода в блоке catch (если ResetAbort выполняется внутри блока catch, он будет другим: он отменит текущий запрос Abort и продолжит выполнение вниз. Поэтому, если вы . хотите гарантировать завершение потока, лучше всего использовать Join, как в приведенном выше примере).
2. Пул потоков
Пул потоков (ThreadPool) — относительно простой метод. Он подходит для коротких задач, требующих нескольких потоков (например, для некоторых потоков, которые часто блокируются). Его недостаток заключается в том, что он не может контролировать созданные потоки и не может быть установлен его приоритет. Поскольку каждый процесс имеет только один пул потоков и, конечно же, каждый домен приложения имеет только один пул потоков (линию), вы обнаружите, что все функции-члены класса ThreadPool статичны! Когда вы вызываете ThreadPool.QueueUserWorkItem, ThreadPool.RegisterWaitForSingleObject и т. д. в первый раз, будет создан экземпляр пула потоков. Ниже приводится введение в две функции пула потоков:
public static bool QueueUserWorkItem( //Возвращает true, если вызов успешен
WaitCallback callBack, // Делегат, вызываемый создаваемым потоком
состояние объекта //Параметры, передаваемые делегату
)//Его другая перегруженная функция аналогична, за исключением того, что делегат не принимает никаких параметров. Функция этой функции состоит в том, чтобы поставить создаваемый поток в очередь в пул потоков, когда количество доступных потоков в пуле потоков не равно нулю. пул потоков создал потоки. Если число ограничено (значение по умолчанию — 25), этот поток будет создан. В противном случае он будет поставлен в очередь в пул потоков и будет ждать, пока не появятся доступные потоки.
public static RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject, //WaitHandle для регистрации
WaitOrTimerCallback callBack, // Делегат вызова потока
состояние объекта, //Параметры, передаваемые делегату
int TimeOut,//Timeout, единица измерения — миллисекунды,
boolexecutOnlyOnce file://Выполнять ли только один раз
);
публичный делегат void WaitOrTimerCallback(
состояние объекта, //то есть параметры, передаваемые делегату
bool timedOut//true означает, что из-за тайм-аута, в противном случае это означает waitObject
);
Функция этой функции заключается в создании потока ожидания. После вызова этой функции этот поток будет создан. Он будет находиться в «заблокированном» состоянии до тех пор, пока параметр waitObject не перейдет в состояние завершения или не наступит установленное время TimeOut. Стоит отметить, что эта «Блокировка» сильно отличается от состояния WaitSleepJoin потока: когда поток находится в состоянии WaitSleepJoin, ЦП регулярно пробуждает его для опроса и обновления информации о состоянии, а затем снова входит в состояние WaitSleepJoin. Переключение потоков очень ресурсоемко; и Поток, созданный с помощью этой функции, отличается. ЦП не переключится на этот поток до его запуска. Это не отнимает время ЦП и не тратит время на переключение потоков, но как это делает ЦП. знаете, когда его запустить? Фактически, пул потоков будет генерировать несколько вспомогательных потоков для мониторинга этих условий срабатывания. Как только условия будут выполнены, соответствующие потоки будут запущены. Конечно, сами эти вспомогательные потоки также отнимают время, но если вам нужно создать больше ожидания. потоки, используйте пул потоков. Преимущества становятся более очевидными. См. пример ниже:
статический AutoResetEvent ev = новый AutoResetEvent (false);
public static int Main(string[] args)
{ ThreadPool.RegisterWaitForSingleObject(
ев,
новый WaitOrTimerCallback (WaitThreadFunc),
4,
2000,
false//Указывает, что таймер будет сбрасываться каждый раз, когда операция ожидания будет завершена, пока не произойдет выход из системы и ожидание.
);
ThreadPool.QueueUserWorkItem (новый WaitCallback (ThreadFunc), 8);
Thread.Sleep (10000);
вернуть 0;
}
общественная статическая пустота ThreadFunc (объект b)
{ Console.WriteLine ("объект – {0}", b);
for(int i=0;i<2;i++)
{ Thread.Sleep (1000);
ев.Установить();
}
}
public static void WaitThreadFunc (объект b, bool t)
{ Console.WriteLine ("объект — {0},t — {1}",b,t);
}
Результатом его работы является:
объект 8
объект равен 4, t — ложь
объект равен 4, t — ложь
объект равен 4, это правда
объект равен 4, это правда
объект равен 4, это правда
Из приведенных выше результатов мы видим, что поток ThreadFunc выполнялся один раз, а WaitThreadFunc выполнялся 5 раз. О причине запуска этого потока мы можем судить по параметру bool t в WaitOrTimerCallback: если t ложь, это означает waitObject, иначе это связано с таймаутом. Кроме того, мы также можем передать некоторые параметры потоку через объект b.
3. Таймер
Он подходит для методов, которые необходимо вызывать периодически. Он не запускается в потоке, создавшем таймер. Он запускается в отдельном потоке, автоматически выделенном системой. Это похоже на метод SetTimer в Win32. Его структура:
публичный таймер(
Обратный вызов TimerCallback, //Метод для вызова
состояние объекта, // параметры, передаваемые в обратный вызов
int DueTime, //Сколько времени потребуется, чтобы начать обратный вызов?
int period//Интервал времени для вызова этого метода
); // Если параметр DueTime равен 0, обратный вызов немедленно выполняет свой первый вызов. Если параметр DueTime имеет значение Infinite, обратный вызов не вызывает свой метод. Таймер отключен, но его можно повторно включить с помощью метода Change. Если period равен 0 или Infinite, а DueTime не Infinite, обратный вызов вызывает свой метод один раз. Периодическое поведение таймера отключено, но его можно повторно включить с помощью метода Change. Если period равен нулю (0) или Infinite, а DueTime не Infinite, обратный вызов вызывает свой метод один раз. Периодическое поведение таймера отключено, но его можно повторно включить с помощью метода Change.
Если мы хотим изменить период и время выполнения таймера после его создания, мы можем изменить его, вызвав метод Change объекта Timer:
public bool Изменение(
интервал времени,
внутренний период
);//Очевидно, что два измененных параметра соответствуют двум параметрам в Timer
public static int Main(string[] args)
{
Console.WriteLine ("период равен 1000");
Timer tm=новый таймер (новый TimerCallback (TimerCall),3,1000,1000);
Нить.Сон (2000);
Console.WriteLine("период равен 500");
tm.Изменить (0,800);
Нить.Сон (3000);
вернуть 0;
}
public static void TimerCall (объект b)
{
Console.WriteLine("обратный вызов таймера; b равно {0}",b);
}
Результатом его работы является:
период 1000
обратный вызов таймера; b равно 3
обратный вызов таймера; b равно 3
период 500
обратный вызов таймера;b равно 3
обратный вызов таймера;b равно 3
обратный вызов таймера;b равно 3
обратный вызов таймера;b равно 3
Подвести итог
Из приведенного выше краткого введения мы видим случаи, когда они используются соответственно: Thread подходит для случаев, когда требуется сложное управление потоками; ThreadPool подходит для коротких задач, требующих нескольких потоков (например, тех, которые часто блокируются). ); Таймер подходит для методов, которые необходимо вызывать периодически. Понимая особенности их использования, мы можем правильно выбрать подходящий метод.