Являются ли асинхронные записи в перенаправленный стандартный вывод синхронными или асинхронными?

#c# #asynchronous #console-application

#c# #асинхронный #консольное приложение

Вопрос:

В документации для консоли указано:

Чтобы перенаправить стандартный ввод, стандартный вывод или стандартный поток ошибок, вызовите консоль.setIn, консоль.SetOut или консоль.Метод setError, соответственно. Операции ввода-вывода, использующие эти потоки, синхронизированы, что означает, что несколько потоков могут считывать или записывать в потоки. Это означает, что методы, которые обычно являются асинхронными, такие как TextReader.ReadLineAsync, выполняются синхронно, если объект представляет консольный поток.

Означает ли это, что даже если вывод перенаправляется в файл, этот код будет выполняться синхронно?

Console.Out.WriteLineAsync("Hello World Async");

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

В качестве альтернативы, может ли кто-нибудь придумать способ, которым я могу определить любой путь?

Комментарии:

1. Просто перенаправьте вывод в программу, которая не считывает стандартный ввод, и посмотрите, что произойдет.

2. Я не совсем уверен, как я это сделаю … но уверен, что буду экспериментировать

3. Просто создайте программу, которая ничего не делает, кроме блокировки навсегда, и перенаправьте на нее. Затем вы увидите, что происходит, когда выходной буфер не используется.

Ответ №1:

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

Да, так и будет.

При вызове Console.SetOut() выполняется этот код:

 public static void SetOut(TextWriter newOut) {
    if (newOut == null)
        throw new ArgumentNullException("newOut");
    Contract.EndContractBlock();
#pragma warning disable 618
    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
#pragma warning restore 618
#if FEATURE_CODEPAGES_FILE    // if no codepages file then we are locked into default codepage  and this field is not used              
    _isOutTextWriterRedirected = true;
#endif
    newOut = TextWriter.Synchronized(newOut);
    lock(InternalSyncObject) {
        _out = newOut;
    }
}
  

Т.е. Новый вывод TextWriter всегда является записью, возвращаемой TextWriter.Syncronized() . Это, в свою очередь, всегда возвращает SyncTextWriter объект. И если вы посмотрите на WriteLineAsync() метод для этого класса, он выполняет две вещи:

  • Это сам по себе синхронизированный метод, что означает, что если несколько потоков попытаются использовать средство записи, фактически это будет делать только один поток одновременно (т. Е. Все методы с этой аннотацией являются взаимоисключающими между потоками).
  • Более уместным для вашего вопроса является то, что WriteLineAsync() метод вызывает синхронный WriteLine() метод для базового TextWriter , который использовался для создания этого SyncTextWriter объекта.

Этот второй момент означает, что метод не вернется, пока операция не будет завершена. Он всегда возвращает завершенную задачу. У него нет способа вернуть незавершенную задачу, и если операция заблокирована по какой-либо причине, например, из-за того, что потребитель выходного потока не использует его и, следовательно, вызывает заполнение выходного буфера, вызов не вернется.

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

Итак, все сказанное:

  • Если вы записываете в файл, будь то потому, что вы вызвали SetOut() или потому, что стандартный вывод процесса был перенаправлен, это может замедлить работу программы в зависимости от того, сколько выходных данных генерирует ваша программа по сравнению с другой выполняемой ею работой, но я сомневаюсь, что вы испытаете что-либо, характеризуемое как «остановка». Т.е.дисковый ввод-вывод выполняется медленно, но обычно он не блокируется в течение длительных периодов времени.
  • Если вы вызываете SetOut() и передаете какой-либо другой тип записи, предположительно, у вас есть контроль над ним и вы можете просто убедиться, что он записан правильно и не блокируется при записи.
  • Если стандартный вывод перенаправляется извне в какое-либо другое место назначения, например, во вторую запущенную программу .NET, которая запустила ваш процесс и перенаправила Process.StandardOutput поток, тогда этой другой программе следует убедиться, что она не отстает, если она заботится о том, чтобы ваша программа могла беспрепятственно продолжать. Но в любом случае это всегда так; потребитель выходных потоков процесса всегда должен следить за тем, чтобы читать их как можно быстрее.

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

Если бы это действительно было проблемой, вы могли бы сами буферизировать вывод в процессе, например, имея BlockingCollection<string> объект для посредничества между потоками, которые хотят записать на вывод, и потоком-потребителем, который выполняет фактическую запись. Но это не все, что полезно; это просто откладывает дело в долгий ящик, потому что вы не хотите продолжать запись в коллекцию бесконечно, если нельзя рассчитывать на то, что потребляющий поток сможет продолжить беспрепятственно. Если потребитель действительно заблокирован, коллекция просто станет слишком большой и OutOfMemoryException в какой-то момент выдаст an . У вас возникнут аналогичные проблемы с любым другим механизмом, который стремится делегировать запись другому потоку с целью позволить текущему потоку работать без блокировки.

ИМХО, лучше просто писать нормально (т. Е. Даже Не утруждать себя использованием ...Async методов, если только это не происходит из-за того, что у вас есть код, который абстрагировал запись как TextWriter и предназначен для асинхронной работы, когда это поддерживается), и рассчитывать на то, что потребляющий конец потока «поступит правильно»и не блокировать ваш процесс.