Как выполняется задача с ожиданием без дополнительных потоков - PullRequest
0 голосов
/ 15 мая 2019

Это дополнительный вопрос к этому вопросу:

Если async-await не создает никаких дополнительных потоков, то как приложение реагирует на запросы?

и к этому сообщению в блоге:

http://blog.stephencleary.com/2013/11/there-is-no-thread.html

Относительно принятого ответа на связанный вопрос. Он описывает шаги того, что происходит, когда вызывается await.

Он пишет на шаге 3 и 4, что когда это происходит, текущий поток возвращается в контекст вызова, который делает цикл сообщений доступным для получения дополнительных сообщений, что делает приложение отзывчивым.И затем на шаге 5 он пишет, что задача, которую мы вызвали, ждет, теперь завершена, и остальная часть исходного метода продолжается после получения нового сообщения, чтобы продолжить остальную часть метода.

Что я не сделалВы понимаете, как именно эта задача была выполнена для начала, если не было нового потока, а исходный поток занят контекстом вызова?

Итак, я обратился к сообщению в блоге, в котором описывается, каквся операция на самом деле выполняется на гораздо более низких уровнях (что, честно говоря, я действительно не знаю, как они работают), а затем просто уведомление о том, что это было сделано ... Но я не понимаю, почему вдруг в случае задачи, вы можете рассчитывать на другое аппаратное обеспечение для своих вычислений, а не на процессор, что каким-то образом позволяет не создавать новый поток. А что, если эта задача представляет собой сложные вычисления, тогда нам не понадобится процессор?И затем мы возвращаемся к тому, что я писал, что текущий поток уже занят контекстом вызова ...

Ответы [ 3 ]

1 голос
/ 15 мая 2019

Но я не понимаю, почему вдруг в случае задачи вы можете рассчитывать на другое оборудование вместо вычислений на ЦП, что каким-то образом позволяет не создавать новый поток.

Поскольку ЦП является не только возможным источником параллелизма в наборе аппаратных устройств.

Потоки логически связаны с ЦП, поэтому для любой связанной с ЦП рабочей нагрузки мы используем потоки либо явно, создавая их, либо используя механизмы более высокого уровня, такие как пул потоков или Task.Run, либо неявно - каждое приложение запускается внутри некоторогопоток по умолчанию в любом случае.

Но есть другой вид операций - операции ввода / вывода, которые подразумевают использование аппаратных устройств, отличных от CPU <-> RAM, таких как диски, сетевые адаптеры, клавиатура, различные периферийные устройства и т. Д. Такие устройства работают с данными.который приходит и выходит асинхронно - никто не знает, когда вы в следующий раз нажмете клавишу или новые данные поступят из сети.Чтобы справиться с асинхронностью, такие аппаратные устройства могут передавать данные без участия ЦП (проще говоря, устройству предоставляется несколько адресов в ОЗУ, где находятся данные, и затем оно может выполнять передачу самостоятельно).Это очень упрощенная картина того, что происходит, но вы можете думать, что на этом большинство асинхронных потоков заканчиваются.Как вы видите, процессор там не требуется, поэтому нет необходимости создавать новый поток.Что касается входящих данных, то механизм очень похож, с той лишь разницей, что, как только данные поступают, они помещаются в определенную область ОЗУ, чтобы быть доступными для дальнейшего использования.Когда устройство завершает переход данных (входящий или исходящий), оно выдает специальный сигнал, называемый прерыванием, чтобы уведомить ЦПУ о завершении операции, и ЦП реагирует на прерывание с помощью запуска выполнения конкретного кода, который обычно находится в драйвере аппаратного устройства - таким образом, драйверМожно отправить уведомление на более высокие уровни.Прерывания могут происходить от устройств асинхронно, и процессор обязан приостановить любое текущее выполнение, которое он выполняет в данный момент, и переключиться на обработчик прерываний.Когда драйвер устройства выполняет обработчик прерывания, он отправляет уведомление о завершении ввода-вывода на более высокие уровни стека ОС, и, наконец, это уведомление попадает в приложение, которое инициировало операцию ввода-вывода.Как это сделать, в основном зависит от ОС, на которой работает приложение.Для Windows существует специальный механизм, называемый Порты завершения ввода-вывода , который подразумевает использование некоторого пула потоков для обработки уведомлений о завершении ввода-вывода.Эти уведомления, наконец, поступают из CLR в приложение и инициируют выполнение продолжений, которые в конечном итоге могут выполняться в отдельном потоке, который является либо потоком из пула потоков ввода-вывода, либо любым другим потоком, в зависимости от конкретной реализации awaiter .

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

1 голос
/ 15 мая 2019

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

Важно отметить, что есть два типа задач: то, что я называю задачами делегирования и задачами обещания . Делегированные задачи - это типы задач, используемые при параллельной обработке - оригинальный стиль использования библиотеки параллельных задач, где задача представляет собой некоторый объем кода, который должен быть выполнен.

Задачи Promise, с другой стороны, предоставляют только уведомление о том, что что-то завершено (вместе с результатом / исключением этой операции). Задачи Promise не выполняют код; они являются объектным представлением обратного вызова. async всегда использует задачи Promise.

Итак, когда метод async возвращает Task, эта задача является задачей Promise. Он не «исполняется», но может «завершать» .

0 голосов
/ 15 мая 2019

Я думаю, вам нужно понять уровень, на котором идет компилятор, чтобы заставить async / await код работать.

Возьми этот метод:

public async Task<int> GetValue()
{
    await Task.Delay(TimeSpan.FromSeconds(1.0));
    return 42;
}

Когда это скомпилировано, вы получите это:

[AsyncStateMachine(typeof(<GetValue>d__1))]
public Task<int> GetValue()
{
    <GetValue>d__1 stateMachine = default(<GetValue>d__1);
    stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <GetValue>d__1 : IAsyncStateMachine
{
    public int <>1__state;

    public AsyncTaskMethodBuilder<int> <>t__builder;

    private TaskAwaiter <>u__1;

    private void MoveNext()
    {
        int num = <>1__state;
        int result;
        try
        {
            TaskAwaiter awaiter;
            if (num != 0)
            {
                awaiter = Task.Delay(TimeSpan.FromSeconds(1.0)).GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    num = (<>1__state = 0);
                    <>u__1 = awaiter;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                    return;
                }
            }
            else
            {
                awaiter = <>u__1;
                <>u__1 = default(TaskAwaiter);
                num = (<>1__state = -1);
            }
            awaiter.GetResult();
            result = 42;
        }
        catch (Exception exception)
        {
            <>1__state = -2;
            <>t__builder.SetException(exception);
            return;
        }
        <>1__state = -2;
        <>t__builder.SetResult(result);
    }

    void IAsyncStateMachine.MoveNext()
    {
        //ILSpy generated this explicit interface implementation from .override directive in MoveNext
        this.MoveNext();
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        <>t__builder.SetStateMachine(stateMachine);
    }

    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
        //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
        this.SetStateMachine(stateMachine);
    }
}

Это IAsyncStateMachine.MoveNext(), позволяющий разрешить выпадающему коду при попадании на await. Обратите внимание на return; в if (!awaiter.IsCompleted).

Это просто конечный автомат, который творит всю магию.

...