2013-02-28 19 views
9

Dostaję dziwny problem podczas korzystania z Console.ReadKey() w programie wielowątkowym.Dziwne zachowanie Console.ReadKey() z wielowątkowością

Moje pytanie brzmi: dlaczego tak się dzieje? Czy to błąd, czy też dlatego, że nadużywam Console? (Zauważ, że konsola jest powinien być threadsafe, according to the documentation.)

Najłatwiej wyjaśnić to z kodem:

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace ConsoleApplication2 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      Console.WriteLine("X"); // Also try with this line commented out. 
      Task.Factory.StartNew(test); 
      Console.ReadKey(); 
     } 

     private static void test() 
     { 
      Console.WriteLine("Entering the test() function."); 
      Thread.Sleep(1000); 
      Console.WriteLine("Exiting the test() function."); 
     } 
    } 
} 

Co sądzisz że będzie wydrukować po uruchomieniu go i don 't naciśnij klawisz?

Odpowiedź jest po prostu to, czego można się spodziewać:

X 
Entering the test() function. 
Exiting the test() function. 

Teraz zakomentuj Console.WriteLine("X") i uruchomić go ponownie (bez naciskania klawisza). Spodziewałem się zobaczyć tego wyjścia:

Entering the test() function. 
Exiting the test() function. 

Zamiast tego widzę nic. Po naciśnięciu klawisza:

Entering the test() function. 

... i to wszystko. Program wychodzi (oczywiście) i nie ma czasu, aby przejść do następnego WriteLine().

Uważam to zachowanie za bardzo tajemnicze. Łatwo się obejść, ale jestem zaintrygowany, dlaczego tak się dzieje.

[EDIT]

jeśli dodać Thread.Sleep(1) bezpośrednio przed Console.ReadKey() to działa zgodnie z oczekiwaniami. Oczywiście nie powinno to być konieczne, ponieważ i tak należy czekać na zawsze.

Wygląda na to, że może to być jakiś rodzaj wyścigu?

Więcej informacji: Servy znalazła (i mam powielone), że linia Console.WriteLine("Entering the test() function.") blokuje się, dopóki nie zostanie naciśnięty żaden klawisz.

Konfiguracja Budowa

Visual Studio 2012, Windows 7 x64, Quad Core, Angielski (UK).

Próbowałem wszystkich kombinacji .Net4, .Net4.5, x86, AnyCPU oraz debugowania i wydawania, i żaden z nich nie działa na moim komputerze. Ale stało się coś naprawdę dziwnego. Zaczęło działać, gdy po raz pierwszy wypróbowałem wersję AnyCPU dla .Net4, ale potem przestało działać. Wydaje się bardzo podobny do stanu wyścigu, który wpływa tylko na niektóre systemy.

+1

Na podstawie moich doświadczeń jest jakaś inicjalizacji, który odbywa się po raz pierwszy konsola jest napisane, że ustawia się ramy do równoczesnego odczytuje i zapisuje. Jeśli konsola została napisana w ogóle przed pierwszym wywołaniem 'ReadKey', wszystko będzie dobrze, ale nigdy nie było napisane, aby to zachowanie było widoczne. Jak powiedziałeś, łatwe do obejrzenia, ale bardzo ciekawe. – Servy

+0

Co się stanie, gdy wyraźnie wymusisz na konsoli. Czy chcesz spłukać? – Krypes

+0

@Krypes Blok "Console.WriteLine" blokuje (w oparciu o moje eksperymenty), a nie kontynuuje i nic nie robi, więc nie ma możliwości opróżnienia go. – Servy

Odpowiedz

15

To jest stan wyścigu. Oto co się dzieje, gdy pierwszy Console.WriteLine tam nie ma:

  1. Zadanie zostało utworzone, ale nie uruchamiać
  2. Console.ReadKey wykonuje, wykonuje blokadę Console.InternalSyncObject i blokuje oczekiwanie na wejście
  3. zadanie za Console.WriteLine wzywa Console.Out, który nazywa Console.InitializeStdOutError dla pierwszej chwili inicjalizacji aby skonfigurować konsolę strumieni
  4. Console.InitializeStdOutError próbuje zablokować na Console.InternalSyncObject, ale Console.ReadKey ma go już, więc blokuje ona
  5. Th e użytkownik naciśnie klawisz i Console.ReadKey powroty, zwalniając blokadę
  6. Wezwanie do Console.WriteLine jest odblokowany, a kończy wykonaniem
  7. wyjść procesu, bo nie ma nic w Main po wywołaniu ReadKey
  8. Pozostałą Kod w zadaniu nie dostać szansę, aby uruchomić

przyczyną tego, że zachowuje się inaczej, gdy Console.WriteLine pozostało tam dlatego, że wezwanie do Console.InitializeStdOutError nie dzieje się równolegle z Console.ReadKey.

Krótka odpowiedź brzmi: tak, nadużywasz konsoli. Można albo zainicjować konsolę samodzielnie (przez dereferencję Console.Out), albo poczekać na zdarzenie po uruchomieniu Zadania, ale przed ReadKey, a następnie mieć sygnał Zadanie wydarzenie po wywołaniu Console.WriteLine za pierwszym razem.

+0

Aha, więc jest zakleszczona. To trochę niegrzeczne, aby trzymać zamek podczas oczekiwania na I/O! :) –

+0

Tak, nie żartuję! Ale konsola jest bardzo problematyczna w kontekście wielowątkowym. –

0

Prawdopodobnie dzieje się tak, ponieważ jest wielowątkowe. Twój główny wątek jest w ruchu i wychodzi, zanim twoje zadanie asynchroniczne ma szansę na zgłoszenie. Gdy główny wątek zostanie zamknięty, wszystkie wątki potomne zostaną zabite.

Co zrobić, jeśli czekasz przed czytaniem klucza ReadKey? czy wyprowadza go poprawnie?

+0

Oczekiwany rezultat PO jest rzeczywiście tym, co powinien zobaczyć, nie powinien w ogóle modyfikować kodu. Podczas gdy "ReadKey" blokuje główny wątek, wątek tła powinien móc pisać na konsoli. – Servy

+0

Masz na myśli thread.leep()? Spróbuję ... [EDIT] To robi różnicę (chociaż nie powinno), więc zaktualizuję OP o informacje. –

2

Potwierdzono wewnętrzny błąd w .NET 4.5. Doniesiono na przykład tutaj: https://connect.microsoft.com/VisualStudio/feedback/details/778650/undocumented-locking-behaviour-in-system-console

To działało w .NET 3.5 i .NET 4.

Więcej informacji: http://blogs.microsoft.co.il/blogs/dorony/archive/2012/09/12/console-readkey-net-4-5-changes-may-deadlock-your-system.aspx

Można użyć prostego obejście init struktur wewnętrznych i uniknąć blokowania. Wystarczy dodać do początku działalności (od @renestein):

Console.Error.WriteLine(); 
Console.WriteLine();