UI Thread & Worker Thread

UI Thread & Worker Thread

A process is an executing instance of an application. A thread is a path of execution within a process.
When you start Notepad, the operating system creates a process and begins executing the primary thread of that process.
When this thread terminates, so does the process. This primary thread is supplied to the operating system by the startup code in the form of a function address.
Usually, it is the address of the main or WinMain function that is supplied.
進程是應用程序的一個實例,線程是進程中執行的一個分支。
比如啓動notepad, OS負責創建進程並開始執行主線程,當主線程結束,進程也就結束。

MFC distinguishes two types of threads: user-interface threads and worker threads.
User-interface threads are commonly used to handle user input and respond to events and messages generated by the user.
A user-interface thread is commonly used to handle user input and respond to user events independently of threads executing other portions of the application.
The main application thread (provided in your CWinApp-derived class) is already created and started for you.
MFC有兩種類型的線程:UI線程和工作線程。
UI線程通常用於獨立的處理(各個線程執行應用程序的其它部分產生的) 用戶輸入和用戶事件。APP主線程已經爲我們創建好了。

Worker threads are commonly used to complete tasks, such as recalculation, that do not require user input.
工作線程通常用於完成任務,不需要用戶輸入,比如計算。

The Win32 API does not distinguish between types of threads; it just needs to know the thread’s starting address so it can begin to execute the thread.
MFC handles user-interface threads specially by supplying a message pump for events in the user interface.
CWinApp is an example of a user-interface thread object, because it derives from CWinThread and handles events and messages generated by the user.

A UI thread is a Single Threading Apartment thread that is used to create various user interface objects (in Winforms, this means Controls).
By convention and rule, a Control may only be accessed from within the thread that was used to create it;
doing otherwise can and will produce unexpected results, from visual oddities all the way to a crash.
UI線程是一個”單元線程”用於創建各種UI對象。按照規則,UI對象只能被創建它的線程訪問,否則將出現爲之結果:從奇怪的現象到奔潰。

Unless you explicitly create more, there is only one UI thread within a Windows Forms application.
While you can create another thread and start a message loop, there are very few reasons why you’d want to do this,
and two different UI threads cannot “talk” to each other any more than any other thread can talk to a UI thread.

The key to “Apartment threading” is that an object which is initialized should be initialized in a particular thread
and all operations on that object must be performed from that same thread.
Trying to do operations on the same object from other threads is not guaranteed to work correctly.
The point of this being that the object can be then written without worrying about incorporating thread-safe operations, because only one thread ever accesses it.
“單元線程”的關鍵是被初始化的對象要在特定的線程裏面初始化,並且所有對這個對象的操作都必須在這同一個線程裏面執行。
若在其它線程,將不能保證正確。正是這一點讓我們可以修改這些對象而無需關心線程安全問題,因爲只有一個線程訪問它。

簡單說,在Window操作系統當中,窗口是屬於所在線程的;也就是說這個窗口在哪個Thread當中Create 的那麼這個窗口就屬於那個Thread。同時窗口的消息處理函數也都會在這個Thread當中被執行的。驗證例子如下:

#define WM_MY_MSG           (WM_USER + 100)
BEGIN_MESSAGE_MAP(CWhatIsUIThreadDlg, CDialogEx)
    ON_MESSAGE(WM_MY_MSG, OnMyMsg)
END_MESSAGE_MAP()
DWORD WINAPI CWhatIsUIThreadDlg::ThreadFun(LPVOID pParam)
{
    CWhatIsUIThreadDlg* pThis = reinterpret_cast<CWhatIsUIThreadDlg*>(pParam);
    if (nullptr != pThis)
    {
        TRACE(_T("SubThread = %u, send start\n"), GetCurrentThreadId());
        pThis->SendMessage(WM_MY_MSG);
        TRACE(_T("SubThread = %u, send end\n"), GetCurrentThreadId());
    }
    return 0;
}
void CWhatIsUIThreadDlg::OnBnClickedOk()
{

    TRACE(_T("MainThread = %u, send start\n"), GetCurrentThreadId());

    //1. Call SendMessage in this same thread
    SendMessage(WM_MY_MSG);

    //2. Call SendMessage in this another thread
    HANDLE hThread = CreateThread(NULL, 0, ThreadFun, this, 0, NULL);
    CloseHandle(hThread);

    TRACE(_T("MainThread = %u, send end\n"), GetCurrentThreadId());
}
LRESULT CWhatIsUIThreadDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)
{
    TRACE(_T("Message handler, threadId = %u\n"), GetCurrentThreadId());
    return 0L;
}
//輸出結果如下:
MainThread = 20248, send start
Message handler, threadId = 20248//UI線程執行
MainThread = 20248, send end
SubThread = 7232, send start
Message handler, threadId = 20248//UI線程執行
SubThread = 7232, send end

A UI thread has a number of characteristics that make it special:
1)Windows has a message queue associated with the thread. This happens as soon as the very first window gets created on the thread.
2)The thread runs a message loop, allowing Windows to dispatch messages to the windows. The message loop gets created as soon as you call Application.Run().
3)COM was initialized on the thread, requesting a single-threaded apartment. An STA is necessary to allow many Windows features to work properly, features that are not thread-safe by design. COM ensures that these features are always called in a thread-safe manner, marshaling the call from a worker thread to the STA thread as needed. Examples of these features are Drag+Drop, the clipboard, the shell dialogs (OpenFileDialog etc).
4)The thread never blocks on any operation, it stays responsive so it can dispatch Windows messages as required to keep the user interface responsive and COM marshaling requests flowing. Making calls to WaitHandle.WaitAny() for example are explicitly forbidden and generate an exception.
The startup thread of a process is almost always selected as the UI thread, although that’s not a hard requirement. The STA state is selected by the [STAThread] attribute on the Main() method.

You can create another UI thread by ensuring that the above requirements are met. That could look like this in a boilerplate WF app:

private void button1_Click(object sender, EventArgs e) {
        var ui = new Thread(() => { Application.Run(new Form2()); });
        ui.SetApartmentState(ApartmentState.STA);
        ui.Start();
}

That creates a second window, running on its own UI thread. Note that closing the startup form doesn’t terminate the app. One typical problem you have with this arrangement is that you’ve now got two separate windows, they are not associated with each other at all. The 2nd window cannot be owned by the 1st, it has its own Z-order independent of the 1st. Difficult to deal with by the user. Except in rare circumstances, this doesn’t improve the user interface at all.

消息進入隊列的過程

操作系統會監控計算機上的鍵盤和鼠標等輸入設備,爲每一個輸入事件生成一個消息。根據事件發生時的情況確定出此消息應該發給哪個窗體對象。

這些生成的消息會統一地先臨時放置在一個“系統消息隊列(system message queue)”中,然後,操作系統有一個專門的線程負責從這一隊列中取出消息,根據消息的目標對象(就是窗體的句柄),將其移動到創建它的UI線程所對應的消息隊列中。

注意,每個UI線程都有一個消息隊列,而不是每個窗體一個消息隊列。

特別注意:操作系統並不會爲每一個線程都創建一個消息隊列。只有當一個線程調用Win32 API中的GDI和User函數時,操作系統纔會將其看成是一個UI線程,併爲它創建一個消息隊列。

另外,消息循環是由UI線程的線程函數啓動的,操作系統不管這件事,它只管爲UI線程創建消息隊列。因此,如果某個UI線程的線程函數中沒有定義消息循環,那麼,它所擁有的窗體是無法正確繪製的。

SendMessage dead lock

LRESULT WINAPI SendMessage(
  _In_ HWND   hWnd,
  _In_ UINT   Msg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message. However, the sending thread will process incoming nonqueued messages while waiting for its message to be processed.
如果目標窗口是由調用SendMessage的函數創建的,則直接調用窗口過程;如果不是,則系統切換至創建窗口的線程,然後調用裏面的窗口過程,此時工作線程阻塞,直至調用窗口過程的線程處理完該消息。

注意:SendMessage是有時進入消息隊列,有時不進入。假如在 Thread A 中有一個 窗口W1,那麼在 Thread A 中往W1 SendMessage,那麼這個消息將不會被放入消息隊列,而是直接調用了W1的消息處理函數來直接處理了這個消息。這是不被放入隊列的情況;假如在 Thread B 中往W1 SendMessage,這個時候消息將被放入到Thread A 的消息隊列當中,Thread A 的消息循環的GetMessage 會Get到這個消息並處理。

看一個dead lock的例子:

class CWhatIsUIThreadDlg : public CDialogEx
{
...
public:
    afx_msg void OnBnClickedOk();
    afx_msg LRESULT OnMyMsg(WPARAM wParam, LPARAM lParam);
    BOOL        m_bIsExit;
private:
    static DWORD WINAPI ThreadFun(LPVOID pParam);
};
#define WM_MY_MSG           (WM_USER + 100)
BEGIN_MESSAGE_MAP(CWhatIsUIThreadDlg, CDialogEx)
    ON_MESSAGE(WM_MY_MSG, OnMyMsg)
END_MESSAGE_MAP()
DWORD WINAPI CWhatIsUIThreadDlg::ThreadFun(LPVOID pParam)
{
    CWhatIsUIThreadDlg* pThis = reinterpret_cast<CWhatIsUIThreadDlg*>(pParam);
    if (nullptr == pThis)
        return 0;

    TRACE(_T("SubThread = %u, send start\n"), GetCurrentThreadId());
    pThis->SendMessage(WM_MY_MSG);
    TRACE(_T("SubThread = %u, send end\n"), GetCurrentThreadId());

    pThis->m_bIsExit = TRUE;

    return 0;
}

void CWhatIsUIThreadDlg::OnBnClickedOk()
{
    TRACE(_T("MainThread = %u, send start\n"), GetCurrentThreadId());
    //1. Call SendMessage in this same thread
    SendMessage(WM_MY_MSG);

    //2. Call SendMessage in this another thread
    HANDLE hThread = CreateThread(NULL, 0, ThreadFun, this, 0, NULL);
    //2.1 dead lock
    /*while (TRUE)
    {
        if (m_bIsExit)
            break;
        Sleep(50);
    }*/
    //2.2 dead lock
    //WaitForSingleObject(hThread, INFINITE);
    //2.3 can solve dead lock problem
    DWORD dwWait = 0;
    do 
    {
        dwWait = MsgWaitForMultipleObjects(1, &hThread, FALSE, 5000, QS_ALLINPUT);//wait five seconds
        switch (dwWait)
        {
        case WAIT_OBJECT_0:
            //handle became signalled!
            break;
        case WAIT_OBJECT_0 + 1:
        {
            //This thread awoke due to sent/posted message, process the message Q
            //There is a message in this thread's queue, so MsgWaitForMultipleObjects returned.
            //Process those messages, and wait again.
            MSG msg;
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {
                if (msg.message != WM_QUIT)
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
                else
                {
                    break;
                }
            }
        }break;
        case WAIT_TIMEOUT:
        {
            TRACE(_T("WARNING: Possible Deadlock detected! ThreadID: %d File: %s Line: %d\n"), GetCurrentThreadId(), _T(__FILE__), __LINE__);
        }break;
        }
    } while (dwWait != WAIT_OBJECT_0);
    CloseHandle(hThread);
    TRACE(_T("MainThread = %u, send end\n"), GetCurrentThreadId());
}
LRESULT CWhatIsUIThreadDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)
{
    TRACE(_T("Message handler, threadId = %u\n"), GetCurrentThreadId());
    return 0L;
}

GetMessage

http://blog.csdn.net/hetoby/article/details/52495294

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章