Потоки в программировании весьма полезная вещь. Многопоточность применяется там, где необходимо разгрузить основной поток от "тяжёлых" задач. Многопоточность в C# имеет свою специфику. В этой статье будет уделено внимание задаче приостановки работы потока и его возобновлению с места остановки.
За работу с потоками в C# отвечают несколько классов: во-первых это класс Thread (System.Threading), представляющий отдельный поток. А во-вторых - это ThreadPool, дающий возможность работы с пулом потоков. Пул потоков предпочтительнее для использования, да и удобнее он в реализации. Пул потоков - это очередь потоков приложения, в эту очередь вы можете вставить свой метод, который будет выполнен в отдельном потоке, когда один из потоков освободится.
Многие современные приложения используют возможность паузы выполнения какой то задачи. В тех же антивирусных сканерах обязательно имеются кнопки паузы для вирусного сканирования и его возобновления. Такие задачи как поиск вирусов в антивирусных комплексах реализованы почти наверняка через многопоточность, так как же приостановить выполнение потока, а потом его опять запустить с того же места? Попробуем в этом разобраться...
Создадим простейшее приложение на C#, которое будет использовать отдельный поток для вывода информации. На форме у нас должны располагаться три кнопки, в свойстве Text которых можно написать: Старт, Пауза, Стоп. Так же нам понадобится метка label.
Разместив необходимые компоненты на форме, можно приступать к кодингу. Объявим несколько глобальных переменных и напишем кое-что в конструктор формы:
EventWaitHandle ew;
bool Stop, Pause;
public Form1()
{
InitializeComponent();
ew = new EventWaitHandle(false, EventResetMode.AutoReset);
Stop = true;
}
Здесь мы объявили объект класса EventWaitHandle, именно с помощью этого класса и происходит сигнализация потоку о необходимости остановке или возобновления своей работы. Так же мы объявили две булевых переменных.
На кнопку Старт повесим код:
private void button1_Click(object sender, EventArgs e)
{
if (Stop)
{
button2.Enabled = button3.Enabled = true;
button1.Enabled = false;
Stop = false;
ThreadPool.QueueUserWorkItem(new WaitCallback((s) =>
{
ThreadMethod(100);
}));
}
else
{
ew.Set();
button3.Enabled = true;
}
}
Так же создадим метод, который будет выполняться в отдельном потоке:
void ThreadMethod(int value)
{
Action<int> act = new Action<int>((v) =>
{
label1.Text = v.ToString();
});
int i = 0;
while (!Stop && i <= value)
{
if (Pause)
{
ew.WaitOne();
Pause = false;
}
Thread.Sleep(500);
i += 1;
if (label1.InvokeRequired)
label1.Invoke(new MethodInvoker(() =>
{
act(i);
}));
else
act(i);
}
}
Настала пора немного рассказать про этот загадочный EventWaitHandle. Экземпляр данного класса имеет много методов, наиболее полезные из которых это - WaitOne() и Set(). WaitOne() служит для приостановки действия потока, а Set - для возобновления его работы с места остановки. По нажатию кнопки Старт у нас в пул потоков помещается метод, который будет выполнен в отдельном потоке. Если вы не знакомы с лямбда-выражениями, то советую вам побыстрее исправлять это, т.к лямбды - чрезвычайно упрощают написание кода. В метод ThreadMethod, который мы запускаем в новом потоке мы передаём в параметре число 100.
Особенность работы с WinForm в C# заключается в том что доступ к компоненту можно получить только из того потока. в котором данный компонент создан, соответственно, если попытаться достучаться из другого потока до свойств компонента не созданного в этом потоке, то велика вероятность возникновения ошибок. Для решения этой проблемы у всех компонентов имеется метод Invoke в который необходимо передать делегат (ссылку на метод). Через свойство InvokeRequired проверяется необходимость доступа через Invoke, если InvokeRequired равен false значит метод можно запустить и без Invoke.
На кнопки Пауза и Стоп повесим код, соответственно:
private void button2_Click(object sender, EventArgs e)//PAUSE
{
Pause = true;
button3.Enabled = false;
button1.Enabled = true;
}
private void button3_Click(object sender, EventArgs e)//STOP
{
Stop = true;
button2.Enabled = false;
button1.Enabled = true;
}
Что же делает наше приложение? По нажатию кнопки Старт у нас запускается поток в котором меняется свойство Text label'a с задержкой в 500mc. При нажатии на Паузу, булева переменная Pause становится равна истине и в ThreadMethhod'e поток приостанавливается при помощи WaitOne(). При нажатии Старт поток возобновляет работу с места остановки. При нажатии Стоп поток останавливается, уничтожается. Вот такое незамысловатое приложение
За работу с потоками в C# отвечают несколько классов: во-первых это класс Thread (System.Threading), представляющий отдельный поток. А во-вторых - это ThreadPool, дающий возможность работы с пулом потоков. Пул потоков предпочтительнее для использования, да и удобнее он в реализации. Пул потоков - это очередь потоков приложения, в эту очередь вы можете вставить свой метод, который будет выполнен в отдельном потоке, когда один из потоков освободится.
Многие современные приложения используют возможность паузы выполнения какой то задачи. В тех же антивирусных сканерах обязательно имеются кнопки паузы для вирусного сканирования и его возобновления. Такие задачи как поиск вирусов в антивирусных комплексах реализованы почти наверняка через многопоточность, так как же приостановить выполнение потока, а потом его опять запустить с того же места? Попробуем в этом разобраться...
Создадим простейшее приложение на C#, которое будет использовать отдельный поток для вывода информации. На форме у нас должны располагаться три кнопки, в свойстве Text которых можно написать: Старт, Пауза, Стоп. Так же нам понадобится метка label.
Разместив необходимые компоненты на форме, можно приступать к кодингу. Объявим несколько глобальных переменных и напишем кое-что в конструктор формы:
EventWaitHandle ew;
bool Stop, Pause;
public Form1()
{
InitializeComponent();
ew = new EventWaitHandle(false, EventResetMode.AutoReset);
Stop = true;
}
Здесь мы объявили объект класса EventWaitHandle, именно с помощью этого класса и происходит сигнализация потоку о необходимости остановке или возобновления своей работы. Так же мы объявили две булевых переменных.
На кнопку Старт повесим код:
private void button1_Click(object sender, EventArgs e)
{
if (Stop)
{
button2.Enabled = button3.Enabled = true;
button1.Enabled = false;
Stop = false;
ThreadPool.QueueUserWorkItem(new WaitCallback((s) =>
{
ThreadMethod(100);
}));
}
else
{
ew.Set();
button3.Enabled = true;
}
}
Так же создадим метод, который будет выполняться в отдельном потоке:
void ThreadMethod(int value)
{
Action<int> act = new Action<int>((v) =>
{
label1.Text = v.ToString();
});
int i = 0;
while (!Stop && i <= value)
{
if (Pause)
{
ew.WaitOne();
Pause = false;
}
Thread.Sleep(500);
i += 1;
if (label1.InvokeRequired)
label1.Invoke(new MethodInvoker(() =>
{
act(i);
}));
else
act(i);
}
}
Настала пора немного рассказать про этот загадочный EventWaitHandle. Экземпляр данного класса имеет много методов, наиболее полезные из которых это - WaitOne() и Set(). WaitOne() служит для приостановки действия потока, а Set - для возобновления его работы с места остановки. По нажатию кнопки Старт у нас в пул потоков помещается метод, который будет выполнен в отдельном потоке. Если вы не знакомы с лямбда-выражениями, то советую вам побыстрее исправлять это, т.к лямбды - чрезвычайно упрощают написание кода. В метод ThreadMethod, который мы запускаем в новом потоке мы передаём в параметре число 100.
Особенность работы с WinForm в C# заключается в том что доступ к компоненту можно получить только из того потока. в котором данный компонент создан, соответственно, если попытаться достучаться из другого потока до свойств компонента не созданного в этом потоке, то велика вероятность возникновения ошибок. Для решения этой проблемы у всех компонентов имеется метод Invoke в который необходимо передать делегат (ссылку на метод). Через свойство InvokeRequired проверяется необходимость доступа через Invoke, если InvokeRequired равен false значит метод можно запустить и без Invoke.
На кнопки Пауза и Стоп повесим код, соответственно:
private void button2_Click(object sender, EventArgs e)//PAUSE
{
Pause = true;
button3.Enabled = false;
button1.Enabled = true;
}
private void button3_Click(object sender, EventArgs e)//STOP
{
Stop = true;
button2.Enabled = false;
button1.Enabled = true;
}
Что же делает наше приложение? По нажатию кнопки Старт у нас запускается поток в котором меняется свойство Text label'a с задержкой в 500mc. При нажатии на Паузу, булева переменная Pause становится равна истине и в ThreadMethhod'e поток приостанавливается при помощи WaitOne(). При нажатии Старт поток возобновляет работу с места остановки. При нажатии Стоп поток останавливается, уничтожается. Вот такое незамысловатое приложение
Исходники: http://dl.dropbox.com/u/16627362/threadPause.rar
Автор: Богданов М.
13.07.2012