По сути, есть два основных поведения, которые вы можете увидеть, когда вызываете BeginFoo () с обратным вызовом.
- Работа начинается в фоновом потоке, и этот поток будет использоваться все время, пока работа не будет завершена и не будет вызван обратный вызов (например, потому что работа является синхронной).
- Хотя некоторая работа выполняется в фоновом потоке, поток не должен использоваться все время (например, потому что работа включает в себя системный ввод-вывод, который может планировать обратные вызовы, например, в IOCompletionPort).
Когда вы используете делегата, вышеописанное поведение # 1 происходит.
Некоторые API (которые имеют базовую поддержку неблокирующих вызовов IO) поддерживают поведение # 2.
В конкретном случае 'Stream' я не уверен, но я предполагаю, что это абстрактный базовый класс, и поэтому это просто поведение по умолчанию для подкласса, который реализует только синхронную версию Read. Хороший подкласс переопределяет BeginRead / EndRead, чтобы иметь неблокирующую реализацию.
Преимущество # 2, как вы сказали, в том, что вы можете иметь, например, 100 ожидающих вызовов ввода-вывода без использования 100 потоков (потоки стоят дорого).