Подтвердить что ты не робот

Может ли компилятор С# различать границы ввода-вывода и вычислительные задачи?

Рассмотрим фрагмент кода, например:

public async Task<Bitmap> DownloadDataAndRenderImageAsync(
    CancellationToken cancellationToken)
{
    var imageData = await DownloadImageDataAsync(cancellationToken);
    return await RenderAsync(imageData, cancellationToken);
}

Первый из шагов этого метода - работа с привязкой ввода/вывода, где вторая, вычислительная.

Когда мы полагаемся на компилятор для создания правильного целевого кода для этой асинхронной операции, что делает компилятор?

В частности, знает ли он, что первый из них связан с I/O, поэтому он должен использовать класс TaskCompletionSource<T>, чтобы между потоком и задачей не было сродства, а для второго оно может использовать любые методов, таких как Run или StartNew или Start, чтобы запланировать задачу в потоке пула потоков?

4b9b3361

Ответ 1

Нет. В приведенном примере компилятор будет использовать TaskCompletionSource<T> (косвенно) для общей асинхронной операции (DownloadDataAndRenderImageAsync). Это до двух методов, которые призваны решать, как они собираются вернуть соответствующую задачу.

Может быть, DownloadImageDataAsync сам по себе является async, который делегирует еще несколько асинхронных операций ввода-вывода. Может быть, RenderAsync вызывает Task.Run. Это как детали реализации, которые компилятор вообще не интересует при компиляции DownloadDataAndRenderImageAsync.

Ответ 2

Когда мы полагаемся на компилятор для создания правильного целевого кода для этой асинхронной операции, что делает компилятор?

В примере, который вы даете компилятору, известно, что DownloadImageDataAsync и RenderAsync - это методы, возвращающие awaitables. Ожидаемые объекты - это объекты, которые могут быть (1) запрошены для завершения, и (2) имеют продолжение, подписанное до их завершения. Компилятор генерирует код, который определяет, завершены ли возвращенные ожидания, а если нет, подписывает оставшуюся часть метода как завершение ожидаемого.

В частности, знает ли он, что первый из них связан с I/O

Неа. Он знает, что он возвращает что-то ожидаемое.

поэтому он должен использовать класс TaskCompletionSource, чтобы не было близости между потоком и задачей

Если вы заботитесь о деталях логики завершения, вы несете ответственность за то, чтобы контекст, в котором происходит ожидание, является правильным контекстом. Если вам все равно, вы получите соответствующий контекст по умолчанию.

а для второго он может использовать любой из методов, таких как Run или StartNew или Start, чтобы запланировать задачу в потоке пула потоков?

Компилятор не делает ничего подобного. Компилятор генерирует код, который проверяет, завершено ли возвращенное ожидание, а если нет, подписывает завершение ожидаемого. Как эти ожидаемые работы отвечают за вызов, а не за компилятор!

Ответ 3

Ни компилятор, ни среда выполнения не знают об этом. Фактически весь термин "связанный с IO" определен неопределенно. Является ли любая ОС вызовом IO?! Спящий или хронометр IO?!

Если вам нужно задать этот вопрос, у вас могут возникнуть некоторые недоразумения вокруг задач, потому что обычно это не нужно знать.

Может быть, распространенная ошибка думать, что ожидание запускает задачу? Он ждет завершения существующей задачи. DownloadImageDataAsync и RenderAsync решить, как и когда выполнить эту задачу. Поэтому они решают, использовать ли CPU или выполнять IO.

Когда DownloadImageDataAsync и RenderAsync передают вам задачу, которую вы не знаете, что внутри, и вам не нужно знать.