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

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