Was denn nun SwitchToThread(), Sleep(0), Sleep(1)?

Was macht man, wenn man keine Wait-Funktionen verwenden will, aber dennoch möchte, dass ein anderer Thread weiterarbeiten kann. Zum Beispiel, weil man einen Spinlock implementieren will.

Nun es gibt insgesamt vier Methoden die durch das Netz geistern.
Ich gehe mal der Häufigkeit nach, die so in manchen Code-Samples finde:

1. __noop;

Wenn der Lock kurz ist, scheint es das beste zu sein, einfach die Schleife weiterlaufen zu lassen und zu hoffen, dass der ein Thread auf einem anderen Prozessor, die Ressource freigibt. Das eignet sich wirklich nur, wenn die Zeitdauer der Sperre als extrem kurz anzusehen ist und eine hohe Anzahl von Prozessoren zur Verfügung steht.
Nach allen Test, die ich gemacht habe, sollte man aber von dieser Art des Wartens bei einem Spinlock absehen. Es schiebt die Leistung des Kerns auf 100% und bringt nichts.

2.  Sleep(0);

Lies sich gut. Schlafe aber eben nicht lange. Man hat auch schon irgendwo gelesen, dass durch diese Methode der Rest der Zeitscheibe dieses Threads aufgegeben wird und ein anderer Thread an die Reihe kommt.
Leider stimmt das nicht ganz ❗
Liest man die Doku genau steht da aber:

A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.

😮 Threads mit höherer oder niedriger Prio haben also nichts davon.

Besonders eklig wird das ganze gerade wenn man Threads unterschiedlicher Prio hat, die hier gegeneinander laufen. Sleep(0); führt in diesem Fall zu einerunnötigen Prozessorlast und eben nicht dazu, dass die Zeitscheibe abgegeben wird. Der Prozess kommt sofort wieder an die Reihe und spin-t weiter.

3. SwitchToThread();

OK. Seit Windows 2000 gibt es diese nette neue Funktion. Damit wird ein anderer Thread aktiv. Egal was für eine Prio er hat. Aber auch diese Funktion tut evtl. nicht genau das was man will.
Auch hier stecken die Tücken im Detail der Doku:

The yield of execution is limited to the processor of the calling thread. The operating system will not switch execution to another processor, even if that processor is idle or is running a thread of lower priority.

Sollte also der Thread, auf den man wartet auf dem anderen Prozessor laufen, so profitiert der nicht von dem Aufruf von SwitchToThread.

4. Sleep(1):

Hiermit erreicht man wirklich was man möchte. Man gibt seine Timeslice auf und erstmal sind die anderen dran.

Mein persönliches Fazit:

Nach meinen Recherchen ist Sleep(1); der vernünftigste Weg seine Zeitscheibe abzugeben. Und nach meinem Dafürhalten ist ein __noop; strickt zu vermeiden. Die Performance ist grottenschlecht.
Das ganze Verhalten hängt extrem auch von den Umständen ab: Zahl der Theads, Häufigkeit der Kollision, Anzahl verfügbare Prozessoren, Verteilung der Prioritäten, Allgemeine Belastung des Systems, Zeitdauer der Sperre etc.

Ich habe mit einigen Parametern gespielt und auch ein kleines Sample gebaut, dass alle 4 oben genannten Funktionen durchprobiert und in dem man auch mit anderen Faktoren (Priorität etc.) spielen kann.
Es zeigte sich, dass Sleep(1); am effektivsten war. Aber dicht auf gefolgt von Sleep(0);, was mich doch etwas überraschte.

Allerdings führen schon kleinste Änderungen (Lockdauer, Zahl der Prozessoren, Spielen mit der Priorität) zu anderen Ergebnissen.
Interessant ist vor allem das Spielen mit den Prioritäten. Man soll nicht glauben, das ein Thread selbst mit niedrigster Prio noch relativ häufig Arbeit bekommt.

Viel Spaß beim Spielen mit dem Code SleepTest

VS-Tipps & Tricks: Springe zur nächsten Klammer funktioniert auch für #if, #elif, #else und #endif

Wer sich schon durch die Windows Header gekämpft hat um herauszufinden warum welche Definition einer Struktur oder Funktion in irgend einer Windows Version so oder gar nicht vorhanden ist, der weiß auch wie einem #if, #elif, #else und #endif das Leben schwer machen können, was die Orientierung betrifft.

Netterweise hilft einem eine Funktion, die man nur von Blöcken und verschachtelten Funktionen her kennt Strg+´ (Edit.GotoBrace). Wichtig! Man darf nicht auf der Variable oder Bedingung stehen, sondern muss auf dem Schlüsselwort stehen.

Wenn man auf einer Präprozessor Direktive kann man mit den Tasten die einem zur passenden Klammer bringt zur nachfolgenden Direktive. Und mit dem Festhalten der Umschalttaste kann man den entsprechenden Block auch markieren.

AfxMessageBox versus CWnd::MessageBox

Jeder MFC Entwickler kennt AfxMessageBox. In der Klasse CWnd finden wir auch die Memberfunktion CWnd::MessageBox.
Mancher Entwickler wird sich nun fragen: Wann nehme ich denn nun was?

Kurze Antwort: Meide CWnd::MessageBox und nutze immer AfxMessageBox

Lange Antwort:
CWnd::MessageBox ist einfach nur ein direkter Wrapper für Windows API Funktion MessageBox.
Der Grund warum man AfxMessageBox verwenden sollte liegt darin was AfxMessageBox einfach noch mehr leistet:

  1. AfxMessageBox benutzt die virtuelle Funktion CWinApp::DoMessageBox. Damit kann man zentral eine andere Behandlung für Fehlermeldungen einbauen.
  2. AfxMessageBox sorgt dafür, dass OLE Controls benachrichtigt werden, dass Ihre nicht modalen Dialoge nun deaktiviert werden müssen. Es wäre ja ziemlich heftig, wenn man solche Dialoge trotz aktiver MessageBox noch benutzen könnte. (siehe CWinApp::DoEnableModeless und Implementierung von CWnd::GetSafeOwner)
  3. CWnd::MessageBox benutzt das aktuelle Fensterhandle aus dem es aufgerufen wird als Parent-Handle für die API Funktion MessageBox. Die wenigsten Entwickler kennen, lesen oder beachten den netten Zusatz in der MessageBox Doku:
    If you create a message box while a dialog box is present, use the handle of the dialog box as the hWnd parameter. The hWnd parameter should not identify a child window, such as a control in a dialog box.
    Es ist also gerade zulässig CWnd::MessageBox aufzurufen, denn oft genug haben wir es mit Child-Windows zu tun.
    Netterweise beachtet AfxMessageBox genau das. Es ermittelt das aktuelle aktive Top-Level Fenster und setzt es intern für den Aufruf von MessageBox.
    Das wird auch ganz besonders wichtig, wenn man evtl. mehrere UI Threads hat. AfxMessageBox verwendet automatisch den richtigen. CWnd::MessageBox verwendet den aktuellen Thread aber evtl. als Fenster das Fenster-Handle aus einem anderen Threads.
  4. CWnd:MessageBox hat keine Implementierung für die F1-Taste und die Hilfeimplementierung der MFC.
  5. Ich muss mich nicht um den Titel der MessageBox kümmern, denn AfxMessageBox benutzt den hinterlegten Applikationsnamen (CWinApp::m_pszAppName) der in der MFC hinterlegt und definiert wurde.
  6. Netterweise setzt AfxMessageBox passende Icons wenn nur die Reaktionsform definiert wird, also kein Icon. (MB_OK und MB_OKCANCEL benutzt MB_ICONEXCLAMATION, MB_YESNO und MB_YESNOCANCEL benutzt MB_ICONQUESTION).

HTH

VS-Tipps&Tricks: Einfache Debug-Ausgabe mit TRACE auch in der Release Version

Wer wollte nicht schon immer mal gerne TRACE (Debug)-Ausgaben in seinem Release Programm haben ohne dafür überall OutputDebugString reinschreiben zu müssen.

Die nachfolgene kleine Klasse macht es möglich, den gewohnten Syntax des MFC TRACE Makros zu verwenden und direkt auf die Debugausgabe umzuleiten:

//    CTraceToOutputDebugString
//        Is a nice replacment class for TRACE
//        Easy to use with:
//            #undef TRACE
//            #define TRACE    CTraceToOutputDebugString()

class CTraceToOutputDebugString
{
public:
    // Non Unicode output helper
    void operator()(PCSTR pszFormat, ...)
    {
        va_list ptr;
        va_start(ptr, pszFormat);
        TraceV(pszFormat,ptr);
        va_end(ptr);
    }

    // Unicode output helper
    void operator()(PCWSTR pszFormat, ...)
    {
        va_list ptr;
        va_start(ptr, pszFormat);
        TraceV(pszFormat,ptr);
        va_end(ptr);
    }

private:
    // Non Unicode output helper
    void TraceV(PCSTR pszFormat, va_list args)
    {
        // Format the output buffer
        char szBuffer[1024];
        _vsnprintf(szBuffer, _countof(szBuffer), pszFormat, args);
        OutputDebugStringA(szBuffer);
    }

    // Unicode output helper
    void TraceV(PCWSTR pszFormat, va_list args)
    {
        wchar_t szBuffer[1024];
        _vsnwprintf(szBuffer, _countof(szBuffer), pszFormat, args);
        OutputDebugStringW(szBuffer);
    }
};

Durch den obenstehenden Code kann man auch in einer Release Version Trace Ausgaben erzeugen und z.B. mit DebugView.exe (Sysinternals) sichtbar machen, ohne evtl. weitere Anpassungen vornehmen zu müssen:

// Activate my special tracer
#undef TRACE
#define TRACE    CTraceToOutputDebugString()

void Foo()
{
     // Sometime usefull to see the output in a release version too
     TRACE(__FUNCTION__ " called at %d\n", GetTickCount());
}

VC-2010: Breaking Changes in der STL

Ich habe mein erstes größeres Projekt mal testweise in VS-2010 übernommen und bin sofort über ein Problem in der STL gestolpert. Ein std::set<FOO>::iterator liefert in VC-2010 jetzt eine const FOO & Referenz!

Das Ändern von Objekten in einem std::set war sowieso nur erlaubt, wenn sich die Reihenfolge nicht ändert.

Die Folge ist, dass der nachfolgende Code in VC-2010 nicht mehr kompiliert.

#include <set>
struct S_FOO
{
    unsigned long     m_dw1, m_dw2;
    // simplified sample without bool operator<
};

class CMySet : public std::set<S_FOO>
{
public:
    void SomethingSpecial()
    {
        // Order must not be changed!
        for (iterator it=begin(); it!=end(); ++it)
            it->m_dw1 = 0;   // <----- C3892!
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    CMySet myset;
    myset.SomethingSpecial();
    return 0;
}

Man erhält den Fehler C3892: ‚it‘ : you cannot assign to a variable that is const

Das und noch einige andere Breaking Changes  wurden gestern in VC-Blog veröffentlicht:

http://blogs.msdn.com/vcblog/archive/2009/05/25/stl-breaking-changes-in-visual-studio-2010-beta-1.aspx

MSDN Abonnenten können ab Montag den 18.05.2009 Visual Studio 2010 Beta 1 herunterladen

Jihad Dannawi kündigt in seinem Blog die Veröffentlichung von Visual Studio 2010 Beta 1 für MSDN Subscriber an:
http://blogs.msdn.com/dannawi/archive/2009/05/15/visual-studio-2010-beta-1-available-for-the-msdn-subscribers-on-monday-may-18th.aspx

Dito kann man es auf ZDNet lesen:
http://blogs.zdnet.com/microsoft/?p=2769

Ich freue mich schon drauf, ich hoffe es findet sich Zeit mal wirklich damit spielen zu können und um herauszufinden ob der Slogan „The new 10 is the next 6“ wirklich trägt… 😉

Refactoring mit Hilfe des Compilers kann eine tückische Sache werden

Wieder mal eine nette Falle: Implizite Konvertierungen und ein Refactoring-Versuch.

Folgende Methoden wurden in einer Klasse verwendet:

...
bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet, long lIdProject,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet,
            CDataConnection &dataConnection,
            CAgvipTableCoreData &coreData);
...

Die dritte Methode passte mir nicht von der Reihenfolge der Argumente. und ich änderte sie wie folgt um:

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            CDataConnection &dataConnection);

Ich habe mich nun einfach darauf verlassen, dass der Compiler mir alle entsprechenden Code Stellen schon anmeckern wird, an denen hier was nicht passt. Da ich noch einiges anderes an der Klasse geändert hatte, dauerte es noch eine Weile bis ich den nächsten Build angeworfen habe, und ehrlich gesagt, habe ich das Refactoring dieser Funktion vergessen.
Typischer Fall von: Zu viel auf einmal & Der Compiler macht einfach nicht was ich will 😉

Was passierte? Nichts ❗
Ich bekam keine Fehlermeldung zu dieser Änderung, denn CDataConnection hat eine implizite Konvertierung auf bool. Die Folge war, dass die erste Signatur der Funktion auch dieser Folge von Argumenten entsprach.

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);

Logisch, dass diese Funktion natürlich eine anderes Verhalten hatte und hier nicht mehr das passierte was ich eigentlich wollte.
Dämlicherweise rutschte diese Änderung auch noch durch die Tests und eine ganze Funktionsgruppe unserer Software wurde lahmgelegt und so ausgeliefert… Ein Bug, dazu noch von der Kategorie vermeidbar.
Was lernen wir:

  1. Es gibt keine fehlerfreie Software!
  2. Die kleinen Änderungen bringen die größten Fehler!
  3. Sich beim Refactoring auf den Compiler zu verlassen kann tückisch werden!

Das Web Browser Control stiehlt den Fokus wenn ein Dokument geladen wurde

Wenn man ein Web Browser Control einbindet und dieses eine Seite lädt, dann wird der Fokus in dieses Browser Control gesetzt. Dagegen ist kein Kraut und keine Notification gewachsen.
Genaugenommen ist nicht das Webbrowser Control schuld, sondern der Scriptcode der auf der Seite läuft und den Fokus umsetzt. Das sieht man schnell wenn man den Moment der WM_KILLFOCUS Nachricht im Debugger abpasst und sich den Stacktrace ansieht.

0013b84c 4a570824 USER32!NtUserSetFocus 
0013b858 4a5ce628 mshtml!CDoc::TakeFocus+0x2a 
0013b880 4a63fc0b mshtml!CElement::BecomeCurrent+0x167 
0013b8b4 4a63fb72 mshtml!CElement::focusHelper+0xcc 
0013b8c0 4a587c85 mshtml!CElement::focus+0x1d 
0013b8cc 4a5d7477 mshtml!Method_void_void+0x17 
0013b94c 4a57fae8 mshtml!CBase::ContextInvokeEx+0x462 
0013b97c 4a575413 mshtml!CElement::ContextInvokeEx+0x72 
0013b9b0 76fa5295 mshtml!CElement::ContextThunk_InvokeEx+0x44 
0013b9e8 76fa5208 jscript!IDispatchExInvokeEx2+0xa9 
0013ba20 76fa5323 jscript!IDispatchExInvokeEx+0x56 
0013ba90 76fa577b jscript!InvokeDispatchEx+0x78 
0013bad8 76fa57c6 jscript!VAR::InvokeByName+0x1c1 
0013bb18 76fa4ab0 jscript!VAR::InvokeDispName+0x43 
0013bb3c 76fa5a14 jscript!VAR::InvokeByDispID+0xfb 
0013bd30 76fa46d8 jscript!CScriptRuntime::Run+0x195b 
0013bdf4 76fa506e jscript!ScrFncObj::Call+0x69 
0013be6c 76fa5f6a jscript!CSession::Execute+0xb8 
0013bf6c 76fa672f jscript!NameTbl::InvokeDef+0x183 
0013c040 76fa5295 jscript!NameTbl::InvokeEx+0xd2

Dummerweise gibt es kein Event mehr, das danach gefeuert wird, wenn der Skript-Code abläuft. Das letzte Event bevor das aktive Fenster den Fokus verliert ist OnDocumentComplete.

Es gibt auch einige Threads die dieses Thema behandeln, aber keine vernünftige Lösung. Von so manchen Timerlösungen halte ich nichts, die da so vorgeschlagen werden, wer weiß schon wann eine Seite geladen ist?
Besonders ärgerlich auch, wenn man das Browser Control nicht mal auf einem sichtbaren Fenster hat, sondern nur in einem versteckten Fenster hält. Auch in diesem Fall verliert das aktive Fenster den Fokus.

Aber mit einem kleinen Trick bekommt man es doch hin ( 🙂 warum sonst schreibe ich den Artikel )

  1. Man baut einen OnDocumentComplete Handler ein.
  2. Wenn das Event eintritt, besorgt man sich mit GetFocus das Fenster, dass aktuell noch den Fokus inne hat.
  3. Nun sendet man mit PostMessage eine selbst definierte Nachricht (#define WM_RESTOREFOCUS (WM_APP+x)) an den Container des Webbrowser Controls und übergibt als wParam einfach das Handle des Fensters, dass man soeben mit GetFocus ermittelt hat.
  4. Nach diesem Event wird der Skript-Code ausgeführt, der den Fokus stiehlt. Das stört uns nicht.
  5. Irgendwann kommt die Messageloop jetzt wieder an die Reihe und zieht die benutzerdefinierte Nachricht WM_RESTOREFOCUS aus der Queue.
  6. Man hat natürlich einen Handler für diese Nachricht im Container des Webbrowser Controls. Dieser macht nun nichts anderes als einen SetFocus auf das HWND Handle auszuführen, das im wParam übergeben wurde. Ein Test zuvor mit IsWindow versteht sich von selbst.

Dadurch, dass die Nachricht in der Message-Queue gepostet wird, wird sie zeitnah ausgeführt sobald wirklich der User wieder eine Chance selbst Eingaben zu machen. Problem zufriedenstellend gelöst.

Das sollte sich sogar mit C# oder VB hinbekommen lassen 😉

Selbst reingelegt beim Test von „XP oder später“

Manche Codezeilen schreibt man ja einfach im Schlaf so in etwa wie diesen hier

static const OSVERSIONINFO &GetOSVersionInfo()
{
 static OSVERSIONINFO osVersionInfo;
 if (osVersionInfo.dwOSVersionInfoSize==0)
 {
  osVersionInfo.dwOSVersionInfoSize = sizeof(osVersionInfo);
  ::GetVersionEx(&osVersionInfo);
 }
 // return pointer to struct
 return osVersionInfo;
}

bool OSIsWinXP()
{
 // Check if OS is XP or later
 const OSVERSIONINFO &osvi= GetOSVersionInfo();
 return (osvi.dwPlatformId & VER_PLATFORM_WIN32_NT)!=0 &&
     osvi.dwMajorVersion>=5 &&
     osvi.dwMinorVersion>=1;
}

Der Sinn und Zweck ist eindeutig. Ich benötige diese Funktion um zu Testen ob Windows XP oder ein späteres OS wie Vista oder Windows 7 installiert ist. Dumm nur das dieser Code dämlich falsch ist.
Die Betriebssysteme haben die folgenden internen Versionsnummern
5.0 – Windows 2000
5.1 – Windows XP
5.2 – Windows Server 2003
6.0 – Windows Vista
6.1 – Windows 7 (Anmerkung: idotisch, dass hier nicht 7.0 verwendet wird)

Als ich den Code schrieb war Windows XP gerade draußen und selbst Windows Server 2003 gerade am Horizont. Dämlicherweise schrieb ich in dem Test osvi.dwMajorVersion>=5 && osvi.dwMinorVersion>=1.
ohne natürlich daran zu denken, dass ein späteres OS wieder mit einer 0 als minor Version kommen könnte.
Dadurch ergibt sich natürlich das der Test für alle Betriebssysteme nach Windows XP funktioniert nur nicht für Windows Vista  weil eben die Minor Version hier 0 ist. Ich war drauf und dran mal wieder einen Bug einzureichen bis mir schlagartig klar war, dass nicht Vista einen Fehler hat sondern mein eigener Code.

Der korrekte Test muss natürlich so lauten:

bool OSIsWinXP()
{
 // Check if OS is XP or later
 const OSVERSIONINFO &osvi= GetOSVersionInfo();
 return (osvi.dwPlatformId & VER_PLATFORM_WIN32_NT)!=0 &&
   (osvi.dwMajorVersion>5 ||    // May be vista or later
       (osvi.dwMajorVersion==5 &&    // that's XP
        osvi.dwMinorVersion>=1));
}

Code wie im Schlaf zu schreiben bringt es manchmal eben nicht. 😉