Console.Write() will hang in WPF, but works in Console application Console.Write() will hang in WPF, but works in Console application wpf wpf

Console.Write() will hang in WPF, but works in Console application


Basic Explanation: It hangs because the buffer Console.Write writes to before the text is displayed is getting full and is not draining out for WPF applications when passing in null characters (\0) for reasons beyond my knowledge.


Detailed Explanation: When you call Console.Write it creates a Handle to output it's data to and eventually calls WriteFile on that handle. The other end of the handle needs to process the data that was written to it then return control to the caller. There are two major differences between WPF and a console application I could find:

First, if you inspect the handle type with a console application you get a handle of type FILE_TYPE_CHAR, from WPF you get FILE_TYPE_PIPE.

Console.Write(msgStr);var cOut = Console.OpenStandardOutput();var handle = cOut.GetType().GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(cOut);var method = Type.GetType("Microsoft.Win32.Win32Native").GetMethod("GetFileType", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);var type = method.Invoke(null, new object[] { handle });Debugger.Break();

Second, how the handle is processed on the receiving end is different. In a console application the handle is read in by conhost.exe, in WPF it is read in by visual studio.

The hang itself comes from the fact that there is limited space in the buffer and only so much text can be queued up before the handle has to block new incoming requests so the existing information can drain out. It appears that the handle for the console application can process large numbers of \0 characters but the handle that WPF generates can not. If that difference is from it being a different kind of handle or from the processor on the other side of the handle reading in data differently I don't know.

Hopefully someone who has more experience than me with the Windows API call WriteFile that can explain the differences between the two handle types and will give us a better idea if this is because of the handle type or because of the receiving program.


This works on my machine without issues (Winodws 8.1 VS2013). The problem has nothing to do with the console application. As Scott explained there is some blocking going on. It works when not run under a debugger

  • As WPF Application
  • As Console Application

It will hang when you are trying to debug the application. The deeper reason is that you are sending \0 over the pipe. A pipe has a send buffer (ca. 4 KB) before it blocks further writes to it. This is what you are seeing as a hanging WriteFile call in kernel32.dll. To be able to block there must be someone who wants to read from your pipe. In that case it is VS trying to get your stdout to the debugger output window. When no one is listening the pipe acts as null device which will never block.

Now back to the question why it does work with all strings except \0? This has something to do how to stop reading from a a pipe. The receiver can stop reading from the pipe when it receives \0 as the only message. This is the signal that the process has exited and no further data will be written to it. With your \0 messages you violate that implicit contract and send further data over the pipe but your client (VS) has stopped listening to you. That is not actually an API but seems to be a common agreement. In .NET yout get for async pipe reads e.g. null as last message back. Other applications (e.g. VS) seem to handle \0 messages in a similar way and assume the writer has exited.

It is legal to simply close the pipe handle in that case ReadFile returns false. Our you can also write a message of 0 bytes length to the pipe. Or you can write a 1024 KB null array to the pipe. It is up the reader of your messages to decide if this is the signal to stop reading from your pipe.

Update 1Since at least one commenter was thinking that logic is not enough here is the result of debugging VS. VS does read from the pipe via ReadFile

vsdebug!CReader::ReadPipe

There is a check if the first byte is 0 which then leads to thread termination. If the first byte is not 0 it is treated as unicode string and copied to a string buffer which is displayed in the debugger output window. You can verify this easily by sending instead e.g. 1000 c chars which will show up in the buffer. Then you can follow the control flow where it differs from 1000 0 bytes. It turns out that the relevant piece is:

0:048> db ebp-4201973f794  00 00 00 00 38 63 71 10-fe 03 00 00 92 82 b5 45  ....8cq........E1973f7a4  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc1973f7b4  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc1973f7c4  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc1973f7d4  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc1973f7e4  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc1973f7f4  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc1973f804  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc

Buffer contains data a ebp-410 where are our c characters. Before that there is the buffer size and file handle stored.

    0:048> u 5667dd48  L50    vsdebug!ReaderThreadStart+0x72:    **5667dd48 80bdf0fbffff00  cmp     byte ptr [ebp-410h],0**  check if first byte is 0     5667dd4f 7446            je      vsdebug!ReaderThreadStart+0xc1 (5667dd97)    5667dd51 899decfbffff    mov     dword ptr [ebp-414h],ebx    5667dd57 897dfc          mov     dword ptr [ebp-4],edi    5667dd5a 8d85f0fbffff    lea     eax,[ebp-410h]    5667dd60 53              push    ebx    5667dd61 53              push    ebx    5667dd62 50              push    eax    5667dd63 8d8decfbffff    lea     ecx,[ebp-414h]    5667dd69 e8f5960200      call    vsdebug!CVSUnicodeString::CopyString (566a7463)    5667dd6e c745fc02000000  mov     dword ptr [ebp-4],2    5667dd75 8b4e30          mov     ecx,dword ptr [esi+30h]    5667dd78 6aff            push    0FFFFFFFFh    5667dd7a ffb5ecfbffff    push    dword ptr [ebp-414h]    5667dd80 e84453f6ff      call    vsdebug!CMinimalStreamEx::AddStringW (565e30c9)    5667dd85 834dfcff        or      dword ptr [ebp-4],0FFFFFFFFh    5667dd89 8d8decfbffff    lea     ecx,[ebp-414h]    5667dd8f 53              push    ebx    5667dd90 e86339f5ff      call    vsdebug!CVSVoidPointer::Assign (565d16f8)    5667dd95 eb03            jmp     vsdebug!ReaderThreadStart+0xc4 (5667dd9a)   ** 5667dd97 897e28          mov     dword ptr [esi+28h],edi ** When 0 go here and sleep 200ms    5667dd9a 68c8000000      push    0C8h    5667dd9f ff151c228056    call    dword ptr [vsdebug!_imp__Sleep (5680221c)]    5667dda5 e978caf9ff      jmp     vsdebug!ReaderThreadStart+0xd4 (5661a822)    5667ddaa e87ffc0d00      call    vsdebug!__report_rangecheckfailure (5675da2e)    5667ddaf cc              int     3    5667ddb0 b9fe030000      mov     ecx,3FEh    5667ddb5 899de4fbffff    mov     dword ptr [ebp-41Ch],ebx    5667ddbb 3bc1            cmp     eax,ecx    5667ddbd 7702            ja      vsdebug!CReader::Stop+0x84 (5667ddc1)    5667ddbf 8bc8            mov     ecx,eax    5667ddc1 53              push    ebx    5667ddc2 8d85e4fbffff    lea     eax,[ebp-41Ch]    5667ddc8 50              push    eax    5667ddc9 51              push    ecx    5667ddca 8d85f0fbffff    lea     eax,[ebp-410h]    5667ddd0 50              push    eax    5667ddd1 ff762c          push    dword ptr [esi+2Ch]    5667ddd4 ff1590218056    call    dword ptr [vsdebug!_imp__ReadFile (56802190)]    5667ddda 85c0            test    eax,eax    5667dddc 0f845a80f9ff    je      vsdebug!CReader::Stop+0x117 (56615e3c)    5667dde2 8b85e4fbffff    mov     eax,dword ptr [ebp-41Ch]    5667dde8 85c0            test    eax,eax    5667ddea 0f844c80f9ff    je      vsdebug!CReader::Stop+0x117 (56615e3c)    5667ddf0 b900040000      mov     ecx,400h    5667ddf5 3bc1            cmp     eax,ecx    5667ddf7 736c            jae     vsdebug!CReader::Stop+0x125 (5667de65)    5667ddf9 889c05f0fbffff  mov     byte ptr [ebp+eax-410h],bl    5667de00 40              inc     eax    5667de01 3bc1            cmp     eax,ecx    5667de03 7360            jae     vsdebug!CReader::Stop+0x125 (5667de65)    5667de05 889c05f0fbffff  mov     byte ptr [ebp+eax-410h],bl    5667de0c 389df0fbffff    cmp     byte ptr [ebp-410h],bl    5667de12 0f842480f9ff    je      vsdebug!CReader::Stop+0x117 (56615e3c)    5667de18 899decfbffff    mov     dword ptr [ebp-414h],ebx    5667de1e c745fc01000000  mov     dword ptr [ebp-4],1    5667de25 8d85f0fbffff    lea     eax,[ebp-410h]    5667de2b 53              push    ebx    5667de2c 53              push    ebx    5667de2d 50              push    eax    5667de2e 8d8decfbffff    lea     ecx,[ebp-414h]    5667de34 e82a960200      call    vsdebug!CVSUnicodeString::CopyString (566a7463)    5667de39 c745fc02000000  mov     dword ptr [ebp-4],2    5667de40 8b4e30          mov     ecx,dword ptr [esi+30h]    5667de43 6aff            push    0FFFFFFFFh    5667de45 ffb5ecfbffff    push    dword ptr [ebp-414h]    5667de4b e87952f6ff      call    vsdebug!CMinimalStreamEx::AddStringW (565e30c9)    5667de50 834dfcff        or      dword ptr [ebp-4],0FFFFFFFFh    5667de54 8d8decfbffff    lea     ecx,[ebp-414h]    5667de5a 53              push    ebx    5667de5b e89838f5ff      call    vsdebug!CVSVoidPointer::Assign (565d16f8)    5667de60 e9d77ff9ff      jmp     vsdebug!CReader::Stop+0x117 (56615e3c)    5667de65 e8c4fb0d00      call    vsdebug!__report_rangecheckfailure (5675da2e)    5667de6a cc              int     3    5667de6b b81a7e5d56      mov     eax,offset vsdebug!ATL::CAtlMap<unsigned long,CScriptNode *,ATL::CElementTraits<unsigned long>,ATL::CElementTraits<CScriptNode *> >::~CAtlMap<unsigned long,CScriptNode *,ATL::CElementTraits<unsigned long>,ATL::CElementTraits<CScriptNode *> >+0x15 (565d7e1a)    5667de70 c3              ret    5667de71 b84c8e5d56      mov     eax,offset vsdebug!ATL::CAtlMap<unsigned long,ATL::CComPtr<IVsHierarchyEvents>,ATL::CElementTraits<unsigned long>,ATL::CElementTraits<ATL::CComPtr<IVsHierarchyEvents> > >::~CAtlMap<unsigned long,ATL::CComPtr<IVsHierarchyEvents>,ATL::CElementTraits<unsigned long>,ATL::CElementTraits<ATL::CComPtr<IVsHierarchyEvents> > >+0x15 (565d8e4c)    5667de76 c3              ret    5667de77 6857000780      push    80070057h    5667de7c e8df0af6ff      call    vsdebug!treegrid::IGridView::CleanupItems (565de960)**    0:048> u 5661a822  ** Jump here    vsdebug!ReaderThreadStart+0xd4:    5661a822 395e28          cmp     dword ptr [esi+28h],ebx    5661a825 74d0            je      vsdebug!ReaderThreadStart+0x26 (5661a7f7)    5661a827 ff7624          push    dword ptr [esi+24h]    5661a82a ff157c228056    call    dword ptr [vsdebug!_imp__SetEvent (5680227c)]    5661a830 53              push    ebx**    5661a831 ff1508218056    call    dword ptr [vsdebug!_imp__ExitThread (56802108)]  ** Stop reading

That is the whole magic around that. The reader simply stops reading and you your application will block when the sender buffer is full. No magic involved. It is all depends on the behaviour of the reader.


Because WPF has no unamanged handle to a Console window. I am unable to see the implementation of the Write method, but you can see on most public properties of static Console class return an IO Error with "Message = "The handle is invalid.\r\n".

If you want to display a console window, in a WPF application you need to execute code in the kernel32.dll unmanaged library.

See No output to console from a WPF application?