Асинхронные методы в C# основываются на концепции, называемой "машиной состояний" и "программистской модели", которая позволяет писать неблокирующий код с использованием ключевых слов `async` и `await`. Давайте рассмотрим это подробнее.
### Как работают асинхронные методы
1. **Асинхронность и ожидание**:
Асинхронные методы (обозначаемые как `async`) могут выполнять операции, которые потенциально могут занять время, не блокируя основной поток выполнения. Когда вы используете `await` для вызова асинхронного метода, управление передается обратно вызывающему потоку, пока ожидается результат.
2. **Машина состояний**:
При компиляции асинхронного метода создается состояние, подобное тому, как работает автомат. Этот автомат отслеживает, где в методе было выполнено ожидание, и когда завершится асинхронная операция, он будет продолжать выполнение с того места, где остановился.
### Работает ли это одновременно?
Асинхронное программирование в C# основано на модели невидимого потока. Это означает, что вы не создаете дополнительные потоки для выполнения асинхронных операций (как это делается с помощью класса `Thread` или `Task.Run`). Асинхронные методы работают с одним потоком, переключаясь между выполнением операций.
### Полное переключение между задачами
В отличие от потоков, компилятор C# не выделяет фиксированное количество времени (как 1 мс) для переключения задач. Он использует механизм состояния и ожидания, чтобы определить, когда следующее действие может быть выполнено. Это означает, что переключения происходят на основе готовности асинхронной операции (например, завершения ввода-вывода), а не на основе строго заданных временных интервалов или количества инструкций.
### Одновременное выполнение синхронного и асинхронного кода
Синхронный и асинхронный код могут выполняться одновременно, однако это зависит от того, как они организованы. Например, если в вашей программе есть блокирующий код (как бесконечный цикл) в одном потоке, он может заблокировать выполнение других операций, пока не завершится. В вашем примере с бесконечными циклами оба метода (`t1` и `t2`) работают в разных потоках, и их выполнение будет параллельным (в зависимости от планировщика потоков).
### Что происходит с бесконечным циклом?
Если у вас есть бесконечный цикл внутри асинхронного метода, это может блокировать выполнение других операций, если он выполняется в потоке, который необходим для выполнения асинхронных операций. Однако если вы запускаете его в `Task.Run`, как в вашем примере, это не повлияет на другие асинхронные операции, так как они могут выполняться в других потоках.
### Компиляция и выполнение кода
Когда вы компилируете асинхронный метод, компилятор создает класс-автомат (также известный как "state machine"), который управляет состоянием выполнения метода. Это позволяет отслеживать, когда метод должен ждать, и когда нужно продолжать его выполнение.
В вашем примере:
```csharp
int[] arr = new int[100_000];
int k = 0;
var t1 = Task.Run(() => { /* бесконечный цикл */ });
var t2 = Task.Run(() => { /* бесконечный цикл */ });
await Task.WhenAny(t1, t2);
```
Оба метода выполняются в фоновом режиме, так как они запущены в разных задачах. Как только одна из задач завершится (что в данном случае может быть сложным из-за бесконечных циклов), `Task.WhenAny` вернет завершившуюся задачу.
Таким образом, асинхронные и многопоточные подходы имеют свои собственные механизмы обработки параллелизма и не блокируют основной поток, но, как вы заметили, в некоторой степени забирают ресурсы из окружающей среды, особенно если используются бесконечные циклы.