0 added
0 removed
Original
2026-01-01
Modified
2026-02-21
1
<p><a>#статьи</a></p>
1
<p><a>#статьи</a></p>
2
<ul><li>11 дек 2019</li>
2
<ul><li>11 дек 2019</li>
3
<li>0</li>
3
<li>0</li>
4
</ul><p>Процессоры могут выполнять программы асинхронно. Объясняем, как это происходит, зачем нужно и что значит для программирования.</p>
4
</ul><p>Процессоры могут выполнять программы асинхронно. Объясняем, как это происходит, зачем нужно и что значит для программирования.</p>
5
<p>vlada_maestro / shutterstock</p>
5
<p>vlada_maestro / shutterstock</p>
6
<p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
6
<p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
7
<p><strong>Асинхронность</strong>,<strong>многопоточность</strong>и <strong>параллелизм</strong> - очень важные аспекты программирования, без которых сложно представить современные компьютеры.</p>
7
<p><strong>Асинхронность</strong>,<strong>многопоточность</strong>и <strong>параллелизм</strong> - очень важные аспекты программирования, без которых сложно представить современные компьютеры.</p>
8
<p>Язык программирования C# предоставляет разработчикам множество инструментов для создания приложений, которые могут работать с большим количеством потоков, выполняя их параллельно и асинхронно. Это одна из причин популярности этого языка, а также главная причина, почему именно он выбран для этой серии статей.</p>
8
<p>Язык программирования C# предоставляет разработчикам множество инструментов для создания приложений, которые могут работать с большим количеством потоков, выполняя их параллельно и асинхронно. Это одна из причин популярности этого языка, а также главная причина, почему именно он выбран для этой серии статей.</p>
9
<p>Сразу разобраться в этих концепциях сложно, поэтому мы начнём с небольшого ликбеза о том, как компьютерные процессоры выполняют операции.</p>
9
<p>Сразу разобраться в этих концепциях сложно, поэтому мы начнём с небольшого ликбеза о том, как компьютерные процессоры выполняют операции.</p>
10
<p>За единицу времени (она называется тик) процессор может выполнить только одну задачу - например, прочитать значение ячейки памяти. Поэтому на то, чтобы выполнить следующие операции, потребуется 4 тика:</p>
10
<p>За единицу времени (она называется тик) процессор может выполнить только одну задачу - например, прочитать значение ячейки памяти. Поэтому на то, чтобы выполнить следующие операции, потребуется 4 тика:</p>
11
<ol><li>Прочитать данные из двух ячеек.</li>
11
<ol><li>Прочитать данные из двух ячеек.</li>
12
<li>Сложить их.</li>
12
<li>Сложить их.</li>
13
<li>Записать результат в другую ячейку.</li>
13
<li>Записать результат в другую ячейку.</li>
14
</ol><p>Это касается только атомарных операций, то есть самых маленьких и неделимых. Более сложные задачи могут состоять из нескольких атомарных. Например, чтобы провести умножение числа<em>A</em>на число<em>B</em>, нужно будет прибавить к числу<em>A</em>само себя<em>B - 1</em>раз.</p>
14
</ol><p>Это касается только атомарных операций, то есть самых маленьких и неделимых. Более сложные задачи могут состоять из нескольких атомарных. Например, чтобы провести умножение числа<em>A</em>на число<em>B</em>, нужно будет прибавить к числу<em>A</em>само себя<em>B - 1</em>раз.</p>
15
<p>5 * 5 = 5 + 5 + 5 + 5 + 5</p>
15
<p>5 * 5 = 5 + 5 + 5 + 5 + 5</p>
16
<p>Ещё больше тиков потребуется на деление, а если операцию нужно провести над числами с плавающей запятой, то даже представить страшно, сколько их нужно.</p>
16
<p>Ещё больше тиков потребуется на деление, а если операцию нужно провести над числами с плавающей запятой, то даже представить страшно, сколько их нужно.</p>
17
<p>Прямо сейчас у вас могут быть открыты десяток вкладок в браузере, плеер, мессенджер, редактор кода и ещё много всего. Поэтому кажется странным, что ничего из этого на самом деле не работает одновременно.</p>
17
<p>Прямо сейчас у вас могут быть открыты десяток вкладок в браузере, плеер, мессенджер, редактор кода и ещё много всего. Поэтому кажется странным, что ничего из этого на самом деле не работает одновременно.</p>
18
<p>Несмотря на то что процессор выполняет только одну задачу за один раз, инженеры и программисты нашли способ распределять время его работы так, чтобы он тратил немного времени на одну задачу, потом переключался на другую и так далее.</p>
18
<p>Несмотря на то что процессор выполняет только одну задачу за один раз, инженеры и программисты нашли способ распределять время его работы так, чтобы он тратил немного времени на одну задачу, потом переключался на другую и так далее.</p>
19
<p>В этом им помогло то, что за одну секунду процессор может выполнять огромное количество операций, поэтому человек не замечает, что они все выполняются по очереди.</p>
19
<p>В этом им помогло то, что за одну секунду процессор может выполнять огромное количество операций, поэтому человек не замечает, что они все выполняются по очереди.</p>
20
<p>Количество тиков измеряется в герцах (Гц) - это единица измерения частоты протекания периодических процессов. Например, если мимо вашего дома раз в секунду проезжает гоночный болид, то его частота будет равна 1 Гц. Если болид проезжает два раза в секунду, то его частота - 2 Гц; если он проезжает трижды, то давно пора подумать о переезде.</p>
20
<p>Количество тиков измеряется в герцах (Гц) - это единица измерения частоты протекания периодических процессов. Например, если мимо вашего дома раз в секунду проезжает гоночный болид, то его частота будет равна 1 Гц. Если болид проезжает два раза в секунду, то его частота - 2 Гц; если он проезжает трижды, то давно пора подумать о переезде.</p>
21
<p>Процессор так быстро выполняет процессы, что его частота измеряется в гигагерцах.</p>
21
<p>Процессор так быстро выполняет процессы, что его частота измеряется в гигагерцах.</p>
22
<p>1 ГГц = 1 000 000 000 Гц</p>
22
<p>1 ГГц = 1 000 000 000 Гц</p>
23
<p>Частота современных процессоров обычно равна 2-3 ГГц.</p>
23
<p>Частота современных процессоров обычно равна 2-3 ГГц.</p>
24
<p>Каждая программа состоит из множества процессов: нужно 500 раз провести сложение, записать данные в 2000 ячеек, перемножить всё это, а потом, наконец, поделить.</p>
24
<p>Каждая программа состоит из множества процессов: нужно 500 раз провести сложение, записать данные в 2000 ячеек, перемножить всё это, а потом, наконец, поделить.</p>
25
<p>Если выполнять всё это линейно, то программы станут очень неудобными. Например, когда вы будете нажимать на кнопку скачивания в браузере, ваш компьютер будет блокироваться до тех пор, пока скачивание не завершится. Хорошо, если вы хотя бы будете видеть, сколько процентов загрузилось.</p>
25
<p>Если выполнять всё это линейно, то программы станут очень неудобными. Например, когда вы будете нажимать на кнопку скачивания в браузере, ваш компьютер будет блокироваться до тех пор, пока скачивание не завершится. Хорошо, если вы хотя бы будете видеть, сколько процентов загрузилось.</p>
26
<p>Тогда код программы будет выглядеть примерно так:</p>
26
<p>Тогда код программы будет выглядеть примерно так:</p>
27
Выполнять цикл, пока есть пакеты для скачивания: Загрузить пакет во временное хранилище; Перенести его из временного хранилища в ячейку по адресу X; Посчитать, какой процент пакетов загружен; Обновить полосу загрузки; Конец цикла.<p>Пока выполняется этот цикл, вы не сможете сделать ничего, что не прописано внутри него. И, как видите, здесь нет строчек вроде<em>проигрывать музыку</em>или<em>листать ленту</em>. Более того, даже строчек для отслеживания движений мышью нет.</p>
27
Выполнять цикл, пока есть пакеты для скачивания: Загрузить пакет во временное хранилище; Перенести его из временного хранилища в ячейку по адресу X; Посчитать, какой процент пакетов загружен; Обновить полосу загрузки; Конец цикла.<p>Пока выполняется этот цикл, вы не сможете сделать ничего, что не прописано внутри него. И, как видите, здесь нет строчек вроде<em>проигрывать музыку</em>или<em>листать ленту</em>. Более того, даже строчек для отслеживания движений мышью нет.</p>
28
<p>Поэтому, чтобы компьютерами было удобно пользоваться, мы делим все выполняемые программы на потоки. Допустим, у нас запущено 10 программ, а процессор работает на скорости 100 тиков в секунду.</p>
28
<p>Поэтому, чтобы компьютерами было удобно пользоваться, мы делим все выполняемые программы на потоки. Допустим, у нас запущено 10 программ, а процессор работает на скорости 100 тиков в секунду.</p>
29
<p>Тогда каждый поток получит по 10 тиков. То есть процессор будет в течение 10 тиков выполнять инструкции от одного потока, а потом переходить к инструкциям другого - и так по кругу. Также у каждого потока есть приоритет: более важные программы будут получать больше тиков.</p>
29
<p>Тогда каждый поток получит по 10 тиков. То есть процессор будет в течение 10 тиков выполнять инструкции от одного потока, а потом переходить к инструкциям другого - и так по кругу. Также у каждого потока есть приоритет: более важные программы будут получать больше тиков.</p>
30
<p>В вашей операционной системе каждая запущенная программа выполняется в отдельном потоке. Это происходит автоматически, потому что так спроектированы ОС. Но когда вы пишете программу, вы также можете создавать новые потоки: это позволит вам делать приложения удобнее для пользователей.</p>
30
<p>В вашей операционной системе каждая запущенная программа выполняется в отдельном потоке. Это происходит автоматически, потому что так спроектированы ОС. Но когда вы пишете программу, вы также можете создавать новые потоки: это позволит вам делать приложения удобнее для пользователей.</p>
31
<p>Чаще всего асинхронность нужна в программах с графическим интерфейсом. В них основная логика и работа с изображением помещены в разные потоки. Поэтому, когда логика занята, вы всё ещё можете пользоваться приложением.</p>
31
<p>Чаще всего асинхронность нужна в программах с графическим интерфейсом. В них основная логика и работа с изображением помещены в разные потоки. Поэтому, когда логика занята, вы всё ещё можете пользоваться приложением.</p>
32
<p>Если же всё это выполняется в одном потоке, то приложение будет подвисать, когда выполняется сложная инструкция. В ОС Windows часто можно заметить, что, когда приложение что-то делает, а вы кликаете на него, то в заголовке окна можно увидеть словосочетание "Не отвечает".</p>
32
<p>Если же всё это выполняется в одном потоке, то приложение будет подвисать, когда выполняется сложная инструкция. В ОС Windows часто можно заметить, что, когда приложение что-то делает, а вы кликаете на него, то в заголовке окна можно увидеть словосочетание "Не отвечает".</p>
33
<p>Это не всегда означает, что приложение зависло. Возможно, оно просто занято решением какой-то сложной задачи, которая выполняется в том же потоке.</p>
33
<p>Это не всегда означает, что приложение зависло. Возможно, оно просто занято решением какой-то сложной задачи, которая выполняется в том же потоке.</p>
34
<p>Чтобы лучше закрепить тему, давайте напишем приложение с использованием асинхронности. Оно будет разделено на два потока<em>(Thread)</em>: первый будет обновлять полосу загрузки, а второй - выполнять саму загрузку.</p>
34
<p>Чтобы лучше закрепить тему, давайте напишем приложение с использованием асинхронности. Оно будет разделено на два потока<em>(Thread)</em>: первый будет обновлять полосу загрузки, а второй - выполнять саму загрузку.</p>
35
using System; using System.Threading; using System.Threading.Tasks; using System.Text; namespace Async { class Program { static int full = 100; static int completed = 0; static int state = 0; static char[] cursors = new char[] { '-', '/', '|', '\\' }; static void Main(string[] args) { LoadAsync(); //Старт загрузки UpdateLoading(); //Старт обновления полосы загрузки Console.ReadKey(); Console.WriteLine(); } static void UpdateLoading() //Этот метод каждые 100 миллисекунд будет стирать старую полосу загрузки, а потом выводить новую { while(completed <= full) { Console.Clear(); state++; if(state == 4) { state = 0; } string loadingBar = GetLoadingString(); Console.WriteLine(loadingBar + " " + cursors[state]); Thread.Sleep(100); } } static string GetLoadingString() //Метод, который создаёт текстовую полосу загрузки { StringBuilder loadingBar = new StringBuilder("["); for(int i = 0; i <= full; i++) { if(i < completed) { loadingBar.Append("#"); } else { loadingBar.Append("."); } } loadingBar.Append($"] {completed} %"); return loadingBar.ToString(); } static async void LoadAsync() //Асинхронный метод, который создаёт поток для выполнения метода Load() { await Task.Run(()=>Load()); //Метод ожидает выполнения метода Load() } static void Load() //Метод загрузки, который каждые 500 миллисекунд прибавляет к значению completed единицу { for(int i = 0; i <= full; i++) { completed++; Thread.Sleep(500); } } } }<p>В следующих статьях вы узнаете, как всё устроено, а пока посмотрим, как это работает:</p>
35
using System; using System.Threading; using System.Threading.Tasks; using System.Text; namespace Async { class Program { static int full = 100; static int completed = 0; static int state = 0; static char[] cursors = new char[] { '-', '/', '|', '\\' }; static void Main(string[] args) { LoadAsync(); //Старт загрузки UpdateLoading(); //Старт обновления полосы загрузки Console.ReadKey(); Console.WriteLine(); } static void UpdateLoading() //Этот метод каждые 100 миллисекунд будет стирать старую полосу загрузки, а потом выводить новую { while(completed <= full) { Console.Clear(); state++; if(state == 4) { state = 0; } string loadingBar = GetLoadingString(); Console.WriteLine(loadingBar + " " + cursors[state]); Thread.Sleep(100); } } static string GetLoadingString() //Метод, который создаёт текстовую полосу загрузки { StringBuilder loadingBar = new StringBuilder("["); for(int i = 0; i <= full; i++) { if(i < completed) { loadingBar.Append("#"); } else { loadingBar.Append("."); } } loadingBar.Append($"] {completed} %"); return loadingBar.ToString(); } static async void LoadAsync() //Асинхронный метод, который создаёт поток для выполнения метода Load() { await Task.Run(()=>Load()); //Метод ожидает выполнения метода Load() } static void Load() //Метод загрузки, который каждые 500 миллисекунд прибавляет к значению completed единицу { for(int i = 0; i <= full; i++) { completed++; Thread.Sleep(500); } } } }<p>В следующих статьях вы узнаете, как всё устроено, а пока посмотрим, как это работает:</p>
36
<p>Здесь можно заметить, что курсор обновляется чаще (каждые 100 мс), чем проценты (каждые 500 мс), как и было записано в коде программы.</p>
36
<p>Здесь можно заметить, что курсор обновляется чаще (каждые 100 мс), чем проценты (каждые 500 мс), как и было записано в коде программы.</p>
37
<p><strong>Асинхронность</strong>,<strong>многопоточность</strong>и <strong>параллелизм</strong>- очень мощные и полезные инструменты, которые каждый день делают нашу жизнь лучше. Однако они хранят в себе массу опасностей, которые могут стать причиной катастрофы (буквально), - этому будет посвящена отдельная статья серии.</p>
37
<p><strong>Асинхронность</strong>,<strong>многопоточность</strong>и <strong>параллелизм</strong>- очень мощные и полезные инструменты, которые каждый день делают нашу жизнь лучше. Однако они хранят в себе массу опасностей, которые могут стать причиной катастрофы (буквально), - этому будет посвящена отдельная статья серии.</p>
38
<p>Учитывая сложность этих инструментов, понадобится не одна статья, чтобы их объяснить. Я постараюсь не только показать, как их использовать, но и помочь на более глубоком уровне понять принципы их работы.</p>
38
<p>Учитывая сложность этих инструментов, понадобится не одна статья, чтобы их объяснить. Я постараюсь не только показать, как их использовать, но и помочь на более глубоком уровне понять принципы их работы.</p>
39
<a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>
39
<a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>