c# 队列和消息队列
介绍 (Introduction)
This article explores the mechanics behind the messaging system in the Windows family of operating systems. It's not so much about giving you a new coding toy to work with as it is about explaining some of the fundamentals of how Windows works, with an eye toward an audience of C# developers. Understanding Windows better in turn, can help you become an even better coder.
本文探讨Windows操作系统家族中消息传递系统背后的机制。 与其说是给您一个新的编码玩具,还不如说它是在解释Windows工作原理的一些基础知识,着眼于C#开发人员。 进而更好地了解Windows,可以帮助您成为更好的程序员。
概念化这个混乱 (Conceptualizing this Mess)
A common way to pass information between different threads or even different processes involves message passing. This is where one thread or process can post a message to be received by another thread or process in a safe manner. Windows uses messages to notify its windows of events like painting, resizing, typing and clicking. Each time one of those actions happens, a message is sent to the window, notifying it of that event, at which point it can process it. Underneath the covers of the Form.MouseMove
event for example, it is being raised in response to a WM_MOUSEMOVE
message from Windows.
在不同线程甚至不同进程之间传递信息的常见方式涉及消息传递。 在这里,一个线程或进程可以安全地发布消息,以供另一线程或进程接收。 Windows使用消息通知其窗口诸如绘画,调整大小,键入和单击之类的事件。 每当这些动作之一发生时,都会向该窗口发送一条消息,通知该事件,然后它可以对其进行处理。 例如,在Form.MouseMove
事件的封面下,响应Windows发出的WM_MOUSEMOVE
消息而引发该事件。
That makes sense for user interface stuff, but we can also use message passing with invisible windows, and define our own messages to post to the window. In this way, we can use one of these invisible windows as a message sink. It gets called whenever it receives a message, and we can hook that. The advantage of using a window for this is that posting and receiving messages to and from windows is thread safe, and works across processes. It should be noted that there are better options for remoting in .NET that don't tie you down to the Windows operating system, but we're exploring this anyway because WinForms, like Windows is built on this.
这对于用户界面来说是有意义的,但是我们也可以使用带有不可见窗口的消息传递,并定义我们自己的消息以发布到窗口。 这样,我们可以将这些不可见窗口之一用作消息接收器。 每当收到消息时都会调用它,我们可以对其进行挂接。 为此使用窗口的优点是,向窗口发送消息和从窗口接收消息是线程安全的,并且可以跨进程工作。 应该注意的是,.NET中有许多更好的远程处理选项,它们不会将您限制在Windows操作系统上,但是无论如何我们都在进行探索,因为WinForms(例如Windows)是基于此构建的。
到底什么是窗户? (Just What is a Window?)
A window is an object that has an associated message queue and potentially, but not always presents user interface. It has an associated "window class" that tells us the kind of window it is, and we can define our own window classes, which we'll be doing. Furthermore, it has a handle that can be used to refer to it, and the window class has a callback mechanism to notify windows of that class of incoming messages. For one or more windows, there is one thread that is spinning a loop, wherein it is getting messages and then dispatching messages. This thread drives all of the associated windows. Usually, all visible windows are created on the application's main thread. Let's explore this a bit more.
窗口是一个具有关联消息队列的对象,并且可能但不总是显示用户界面。 它有一个关联的“窗口类”,它告诉我们窗口的类型,并且我们可以定义自己的窗口类,我们将要做。 此外,它具有可用于引用它的句柄,并且窗口类具有回调机制,用于将输入消息通知给该类的窗口。 对于一个或多个窗口,有一个线程正在旋转一个循环,在该循环中,它先获取消息,然后分派消息。 该线程驱动所有关联的窗口。 通常,所有可见窗口都是在应用程序的主线程上创建的。 让我们进一步探讨一下。
Let's look at a simple C program to register a custom window class and then display a window. This is a Microsoft example for doing exactly that. Don't worry if you don't know C, just follow along as much as you can as I'll be explaining and we'll get to some C# code eventually anyway, I promise:
让我们看一个简单的C程序,注册一个自定义窗口类,然后显示一个窗口。 这是一个Microsoft 实例 ,正是这样做的。 如果您不了解C,请不要担心,只要我会解释就尽可能多地遵循,我保证最终还是会得到一些C#代码:
HINSTANCE hinst;
HWND hwndMain;
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;
WNDCLASS wc;
UNREFERENCED_PARAMETER(lpszCmdLine);
// Register the window class for the main window.
if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL,
IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL,
IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWndClass";
if (!RegisterClass(&wc))
return FALSE;
}
hinst = hInstance; // save instance handle
// Create the main window.
hwndMain = CreateWindow("MainWndClass", "Sample",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL,
(HMENU) NULL, hinst, (LPVOID) NULL);
// If the main window cannot be created, terminate
// the application.
if (!hwndMain)
return FALSE;
// Show the window and paint its contents.
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);
// Start the message loop.
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Return the exit code to the system.
return msg.wParam;
}
So there's three or four basic steps here, depending on what you need:
因此,这里需要三个或四个基本步骤,具体取决于您的需求:
Fill a
WNDCLASS
struct with details about the window class, including its name, "MainWndClass", its associated callback procedure, and some styles, which usually don't matter unless the window presents a UI. Once created, register the window class with the OS.在
WNDCLASS
结构中填充有关窗口类的详细信息,包括其名称,“ MainWndClass”,其关联的回调过程以及某些样式,除非窗口提供UI,否则通常不重要。 创建后,在操作系统中注册窗口类。Create the window using our registered window class, several style flags, our program's instance handle (
hinst
) and a title "Sample".使用我们注册的窗口类,几个样式标志,程序的实例句柄(
hinst
)和标题“ Sample”创建窗口。Show the window and paint it. This only applies if the window is to present a UI. We won't be. It's almost always better to use a
Form
in that case anyway, which wraps all of this.显示窗口并将其绘制。 仅在窗口要显示UI时适用。 我们不会。 无论如何,在这种情况下使用
Form
几乎总是更好的做法,因为它包装了所有这些内容。- Finally start the message loop. The message loop as I said gets messages and then dispatches messages. Here, we see it translates messages, too which is primarily way for Windows to deal with keyboard "accelerator" shortcuts. Finally, it dispatches messages. This calls the WndProc window's callback function we passed earlier for every message that is received. Without a message loop, a window would be "frozen", as it could not respond to messages so it would never know when to move, or paint or resize, or process user input. Once again, a thread's message loop can drive several windows. 最后启动消息循环。 我说过的消息循环获取消息,然后分派消息。 在这里,我们看到它也可以翻译消息,这也是Windows处理键盘“加速器”快捷方式的主要方式。 最后,它调度消息。 这将调用WndProc窗口的回调函数,该回调函数是我们先前针对收到的每条消息传递的。 没有消息循环,窗口将被“冻结”,因为它无法响应消息,因此它将永远不知道何时移动,绘制或调整大小或处理用户输入。 线程的消息循环可以再次驱动多个窗口。
现在如何处理消息循环? (Now What About A Message Loop?)
When you call Application.Run()
in a .NET WinForms application, notice it blocks until your application exits. Inside Application.Run()
, most likely buried under layers of fluff is one these message loops. It looks something like while(0!=GetMessage(ref msg)) { TranslateMessage(ref msg); DispatchMessage(ref msg); ... }
, basically like before in the C code. That loop is what's tying up your program on the Application.Run()
call. Every single Form
you create will share that same thread's message loop. This thread is called the UI thread. The UI thread is important because it's the one "the user sees" and other threads typically need to communicate with it in order to accept or update information to and from the UI, but they must do so in a thread safe manner. That's why we use message passing, which can post and receive messages safely accross threads. We can communicate from an auxiliary thread back to the UI thread that way, and then the UI thread itself can use that information to update the UI window, which is then a safe operation since it isn't doing anything from the auxiliary thread at that point.
当您在.NET WinForms应用程序中调用Application.Run()
时,请注意它会阻塞,直到您的应用程序退出。 在Application.Run()
内部,最有可能埋在绒毛层下面的是这些消息循环之一。 看起来像while(0!=GetMessage(ref msg)) { TranslateMessage(ref msg); DispatchMessage(ref msg); ... }
while(0!=GetMessage(ref msg)) { TranslateMessage(ref msg); DispatchMessage(ref msg); ... }
while(0!=GetMessage(ref msg)) { TranslateMessage(ref msg); DispatchMessage(ref msg); ... }
,基本上像以前的C代码一样。 该循环是在Application.Run()
调用上绑定程序的原因。 您创建的每个Form
都将共享同一线程的消息循环。 该线程称为UI线程。 UI线程很重要,因为它是“用户看到的”线程,其他线程通常需要与之通信才能接受UI信息或从UI更新信息,但是它们必须以线程安全的方式进行。 这就是我们使用消息传递的原因,它可以跨线程安全地发送和接收消息。 我们可以通过这种方式从辅助线程传递回UI线程,然后UI线程本身可以使用该信息来更新UI窗口,这是一种安全的操作,因为它当时没有从辅助线程执行任何操作点。
We won't be creating our own message loop in this article, but we'll be doing the rest. We'll also "tap into" the UI thread's message loop by creating another window on that thread, and then receive messages on it. That way, at the end of the day, we will get our code to execute from somewhere inside Application.Run()
's loop.
在本文中,我们不会创建自己的消息循环,但其余部分将继续。 通过在该线程上创建另一个窗口,然后在该线程上接收消息,我们还将“进入” UI线程的消息循环。 这样,最终,我们将使代码从Application.Run()
循环内的某个位置执行。
消息是什么样的? (What's a Message Look Like?)
A message consists of an int
message identifier, and two IntPtr
parameters whose meanings vary depending on what the message identifier is. The good news is, that's all it is. The bad news is, that's all it is. If you need to pass more information than you can wedge into two IntPtr
s, then you'll have to figure out a way to break it across multiple messages, or allocate from the unmanaged heap and use that to hold the parameters, passing the pointer as one of the IntPtr
s. That last technique does not work across process.
一条消息由一个int
消息标识符和两个IntPtr
参数组成,这些参数的含义取决于消息标识符是什么。 好消息是,仅此而已。 坏消息是,仅此而已。 如果您需要传递的信息比楔入两个IntPtr
的信息多,那么您将必须找出一种方法将其拆分为多条消息,或者从非托管堆中进行分配并使用它来保存参数,并传递指针作为IntPtr
之一。 最后一种技术不适用于整个过程。
我们要和他们做什么? (What Are We Going to Do With Them?)
In the demo app, we have two fundamental portions, on the left and then on the right, respectively. On the left is a simple demonstration of the interthread communication. On the right is a simple demonstration of interprocess communication. The left side spawns a task every time you click "Run Task", and the task communicates the progress back to the UI thread using the message queue. On the right, you can choose another instance of the app to connect to (if any others exist), launch another instance, and then use the ListBox
and the provided NumericUpDown
control to send whichever number you set to the window that is selected in the list box. The other application will then show a message box confirming the message receipt. The ListBox
only shows window handles, which aren't very friendly, It would be possible to make some sort of friendly application instance numbering system for these but it's a lot of added complexity and would distract from the core goals here.
在演示应用程序中,我们有两个基本部分,分别在左侧和右侧。 左侧是线程间通信的简单演示。 右边是进程间通信的简单演示。 每次单击“ 运行任务 ”时,左侧都会产生一个任务,该任务会使用消息队列将进度传达回UI线程。 在右侧,您可以选择要连接到的应用程序的另一个实例(如果存在其他实例),启动另一个实例,然后使用ListBox
和提供的NumericUpDown
控件将您设置的任何数字发送到在列表框。 然后,另一个应用程序将显示一个消息框,确认消息接收。 ListBox
仅显示不是非常友好的窗口句柄,可以为它们创建某种友好的应用程序实例编号系统,但会增加很多复杂性,并且会分散这里的核心目标。
编码此混乱 (Coding this Mess)
MessageWindow类别 (The MessageWindow Class)
First up, we must create a MessageWindow
class which deals with all the window mechanics, including creation, receiving and posting messages, and enumerating other windows. I tried to use System.Windows.Forms.NativeWindow
for this initially, which probably would have worked except it has no facility for registering a custom window class, and we need that for our interprocess communication. I could have most likely hacked my way around it, but with all the other P/Invoke calls I needed to make against the window anyway it only would have saved me a trivial amount of code, and I didn't want to run into unforseen limitations using it later, since we're going off book with it. Let's look at the constructor, since the code here is similar to part of the C code we saw before:
首先,我们必须创建一个MessageWindow
类,该类处理所有窗口机制,包括创建,接收和发布消息以及枚举其他窗口。 最初,我尝试使用System.Windows.Forms.NativeWindow
进行操作,但它没有注册自定义窗口类的功能,并且在进行进程间通信时,我们可能会使用它。 我很可能已经破解了它,但是无论如何我需要在窗口上进行所有其他P / Invoke调用,但这只会为我节省一些琐碎的代码,而且我不想遇到无法预料的情况限制以后再使用它,因为我们将不再使用它。 让我们看一下构造函数,因为这里的代码类似于我们之前看到的C代码的一部分:
if (string.IsNullOrEmpty(className))
className = "MessageWindow";
_wndProc = WndProc;
// Create WNDCLASS
var wndclass = new WNDCLASS();
wndclass.lpszClassName = className;
wndclass.lpfnWndProc = _wndProc;
var classAtom = RegisterClassW(ref wndclass);
var lastError = Marshal.GetLastWin32Error();
if (classAtom == 0 && lastError != ERROR_CLASS_ALREADY_EXISTS)
{
throw new Exception("Could not register window class");
}
// Create window
_handle = CreateWindowExW(
0,
wndclass.lpszClassName,
String.Empty,
0,
0,
0,
0,
0,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero
);
Notice we're registering a WNDCLASS
and creating the window like we did before in C. There's no message loop this time, though. That's because for this project we're using the message loop inside Application.Run()
. There's no flag or other setting for that. Basically, Windows "knows" which windows belong to which message loops, based on the thread they were created on so we don't need to specify which one we're using. All we have to do is create the window on the main thread. I want to note that the MessageWindow
code originally came from MoreChilli over at StackOverflow. Since it was well written, it saved me some time since all I had to do was adapt it for my purposes rather than write it from scratch.
注意,我们正在注册WNDCLASS
并像以前在C中一样创建窗口。不过,这次没有消息循环。 这是因为对于该项目,我们在Application.Run()
使用了消息循环。 没有标志或其他设置。 基本上,Windows根据创建它们的线程“知道”哪些窗口属于哪个消息循环,因此我们无需指定正在使用的窗口。 我们要做的就是在主线程上创建窗口。 我要指出的是, MessageWindow
代码最初来自MoreChilli了在StackOverflow上 。 由于它写的很好,因此节省了我一些时间,因为我要做的只是针对我的目的对其进行改编,而不是从头开始编写。
Let's move on to some other important bits of this class. Let's look at our WndProc
window procedure callback:
让我们继续该类的其他一些重要方面。 让我们看一下WndProc
窗口过程回调:
IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
{
if (msg >= WM_USER && msg < WM_USER + 0x8000)
{
var args = new MessageReceivedEventArgs(msg - WM_USER, wParam, lParam);
MessageReceived?.Invoke(this, args);
if (!args.Handled)
return DefWindowProcW(hWnd, msg, wParam, lParam);
return IntPtr.Zero;
}
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
This routine gets called whenever a message is posted to our message window. Here, we're looking for Windows message ids that are within the range of WM_USER
(0x400) to WM_USER+0x7FFF
. If so, we create a new MessageReceivedEventArgs
with our message information, subtracting WM_USER
from msg
to make it zero based, and then we raise the MessageReceived
event. After the event, we check if any of the event sinks have set the value Handled
to true
. If so, we don't call the default window procedure for the window, since we don't want to pass it along for further handling. The reason for this WM_USER
stuff is because we want to define our own window messages, and messages in the range we accept are the range of "user defined" window messages reserved by Windows. We don't want to handle things like mouse movement messages because this window does not present a user interface. We don't care about the standard messages we could be receiving - just the user defined ones.
每当将消息发布到我们的消息窗口时,都会调用此例程。 在这里,我们正在寻找WM_USER
(0x400)到WM_USER+0x7FFF
范围内的Windows消息ID。 如果是这样,我们将使用消息信息创建一个新的MessageReceivedEventArgs
,从msg
减去WM_USER
以使其从零开始,然后引发MessageReceived
事件。 事件发生后,我们检查是否有任何事件接收器将值Handled
设置为true
。 如果是这样,我们不会为窗口调用默认的窗口过程,因为我们不想将其传递给进一步的处理。 之所以使用WM_USER
是因为我们要定义自己的窗口消息,而我们接受的范围内的消息就是Windows保留的“用户定义”窗口消息的范围。 我们不希望处理诸如鼠标移动消息之类的事情,因为此窗口不显示用户界面。 我们不在乎我们可能收到的标准消息,而只是用户定义的消息。
Another really important feature of MessageWindow
is the enumeration of all message windows active on the current machine. This is provided for the IPC demo. We need to see all the available windows so we can present the list to the user, which they can then use to communicate with the foreign MessageWindow
:
MessageWindow
另一个真正重要的功能是枚举当前计算机上所有活动的消息窗口。 这是为IPC演示提供的。 我们需要查看所有可用的窗口,以便向用户展示列表,然后他们可以使用该列表与外部MessageWindow
进行通信:
public static IReadOnlyList<IntPtr> GetMessageWindowHandlesByClassName(string className)
{
if (string.IsNullOrEmpty(className))
className = "MessageWindow";
var result = new List<IntPtr>();
var sb = new StringBuilder(256);
EnumWindows(new EnumWindowsProc((IntPtr hWnd, IntPtr lParam) =>
{
GetClassNameW(hWnd, sb, sb.Capacity);
if (className == sb.ToString())
{
result.Add(hWnd);
}
return true;
}), IntPtr.Zero);
Thread.Sleep(100);
return result;
}
This is a weird routine because of the way EnumWindows()
works. You'd think an enumeration of windows would have a clear count to it but it doesn't. It calls you back repeatedly with new window handles, never saying when it has given you all of them. Because of this, we have to Sleep()
for 100ms to give it time to enumerate. This should be more than enough time. I don't like this, but there's not much to be done. Note that we're comparing the window class name of each returned window with the passed in className
before we add it. You can also pass a className
to the MessageWindow
constructor. They have to match. This way, you can create new message windows with different class names, and then get the list of the windows with the class name you want.
由于EnumWindows()
工作方式,这是一个奇怪的例程。 您可能会认为Windows枚举有一个清晰的计数,但事实并非如此。 它使用新的窗口句柄反复给您回叫,但从未说何时给您所有的窗口句柄。 因此,我们必须在Sleep()
保持100ms的时间来进行枚举。 这应该是足够的时间。 我不喜欢这样,但是没有太多事情要做。 请注意,在添加之前,我们正在将每个返回的窗口的窗口类名称与传入的className
进行比较。 您还可以将className
传递给MessageWindow
构造函数。 他们必须匹配。 这样,您可以创建具有不同类名的新消息窗口,然后获取具有所需类名的窗口列表。
PostMessage()
posts a message to the window in a thread safe manner. PostRemoteMessage()
posts a message to another window in a thread safe manner. They both use the Win32 PostMessage()
call under the covers in order to work:
PostMessage()
以线程安全的方式将消息发布到窗口。 PostRemoteMessage()
以线程安全的方式将消息发布到另一个窗口。 他们都使用Win32 PostMessage()
调用来工作:
public void PostMessage(int messageId, IntPtr wParam, IntPtr lParam)
{
PostMessage(_handle, messageId + WM_USER, wParam, lParam);
}
public static void PostRemoteMessage
(IntPtr hWnd, int messageId, IntPtr parameter1, IntPtr parameter2)
{
PostMessage(hWnd, messageId + WM_USER, parameter1, parameter2);
}
The main difference between the two methods is PostRemoteMethod()
is static
and takes a window handle to a remote window - actually any window, as Windows doesn't care if it's local to the process or not.
两种方法之间的主要区别是PostRemoteMethod()
是static
并且将窗口句柄带到远程窗口-实际上是任何窗口,因为Windows不在乎该进程是否在本地。
The rest of the code is just P/Invoke definitions and miscellaneous fluff.
其余代码只是P / Invoke定义和其他杂项。
演示应用 (The Demo Application)
The demo as I implied before, allows you to use MessageWindow
to receive messages from other threads or processes. The demo code also has facilities for transmitting messages to other processes. It uses MessageWindow
to do the heavy lifting. First, we create it in the form's constructor, and add the corresponding code to safely destroy it in the OnClosed()
virtual method:
如前所述,该演示使您可以使用MessageWindow
从其他线程或进程接收消息。 演示代码还具有将消息传输到其他进程的功能。 它使用MessageWindow
进行繁重的工作。 首先,我们在表单的构造函数中创建它,并添加相应的代码以在OnClosed()
虚拟方法中安全销毁它:
MessageWindow _msgWnd;
public Main()
{
InitializeComponent();
_msgWnd = new MessageWindow("MainMessageWindow");
_msgWnd.MessageReceived += _msgWnd_MessageReceived;
ProcessListBox.Items.Clear(); // sanity
foreach (var hWnd in MessageWindow.GetMessageWindowHandlesByClassName("MainMessageWindow"))
if(_msgWnd.Handle!=hWnd)
ProcessListBox.Items.Add(hWnd.ToInt64().ToString());
}
protected override void OnClosed(EventArgs e)
{
if (null != _msgWnd)
{
_msgWnd.Dispose();
_msgWnd = null;
}
base.OnClosed(e);
}
Note how we gave the window the window class name of "MainMessageWindow". We also hook _msgWnd
's MessageReceive
event so that we can respond to incoming messages. After that, we clear the list just in case (though it should be clear, if you add stuff to it in the designer it won't be), and enumerate all the windows on the system except our own, populating the ListBox
with their values.
注意我们如何为窗口赋予窗口类名称“ MainMessageWindow”。 我们还挂钩_msgWnd
的MessageReceive
事件,以便我们可以响应传入的消息。 在那之后,我们清除列表,以防万一(尽管它应该是清楚的,如果你在设计中添加的东西给它,它也不会),并且枚举系统上的所有窗口,除了我们自己,填充ListBox
与他们价值观。
As mentioned, our MessageReceived
handler covers responding to the incoming window messages on _msgWnd
:
如前所述,我们的MessageReceived
处理程序涵盖了对_msgWnd
入的窗口消息的_msgWnd
:
void _msgWnd_MessageReceived(object sender, MessageReceivedEventArgs args)
{
switch(args.MessageId)
{
case MSG_REMOTE:
MessageBox.Show("Value is " + args.Parameter1.ToInt32().ToString(),
"Remote message received");
args.Handled = true;
break;
case MSG_PROGRESS:
var ctrl =
TaskPanel.Controls[args.Parameter1.ToInt32()-1] as WorkerProgressControl;
if(null!=ctrl)
ctrl.Value = args.Parameter2.ToInt32();
args.Handled = true;
break;
}
}
Here we have two possibilities that we care about: receiving a remote message, and receiving a message on the progress of any currently executing tasks from other threads. In the first possibility, we simply show a message box displaying the first parameter of our message which contains the value specified by the remote process. This will be the value in the remote process' NumericUpDown
control, which we'll get to.
在这里,我们关心的是两种可能性:接收远程消息,以及从其他线程接收有关当前正在执行的任务进度的消息。 在第一种可能性中,我们仅显示一个消息框,其中显示了消息的第一个参数,其中包含由远程进程指定的值。 这将是我们将要获取的远程进程的NumericUpDown
控件中的值。
The second possibility is a message that tells us to update to corresponding task's ProgressBar
. This message comes from a local thread that represents the task. The first parameter contains the task's id, so we know which progress bar to update. The second parameter is the progress bar value. Since the MessageReceived
event fires on the UI thread, because it's called from inside Application.Run()
we are safe to update our Form
's controls.
第二种可能性是一条消息,告诉我们更新到相应任务的ProgressBar
。 此消息来自代表任务的本地线程。 第一个参数包含任务的ID,因此我们知道要更新哪个进度条。 第二个参数是进度条值。 由于MessageReceived
事件在UI线程上触发,因为它是从Application.Run()
内部调用的,所以我们可以安全地更新Form
的控件。
We have two more bits of code to cover, the first being our code to send a remote message, which we do whenever the value of our NumericUpDown
control's Value
changes:
我们还有另外两部分代码需要介绍,第一部分是发送远程消息的代码,每当NumericUpDown
控件的Value
更改时,我们都会执行此操作:
void TransmitUpDown_ValueChanged(object sender, EventArgs e)
{
if(-1< ProcessListBox.SelectedIndex)
{
MessageWindow.PostRemoteMessage(
new IntPtr(int.Parse(ProcessListBox.SelectedItem as string)),
MSG_REMOTE,
new IntPtr((int)TransmitUpDown.Value),
IntPtr.Zero);
}
}
Here, we check if one of the handles in our ListBox
is selected, and if it is, we post a remote message with the id of MSG_REMOTE
to the selected handle sending the Value
as the first message parameter. That will cause the remote process's MessageReceived
event to fire and the corresponding MessageBox
we covered earlier to be shown.
在这里,我们检查是否选择了ListBox
一个句柄,如果已选择,则将ID为MSG_REMOTE
的远程消息MSG_REMOTE
到选定的句柄,并发送Value
作为第一个消息参数。 这将导致远程进程的MessageReceived
事件触发,并显示我们前面介绍的相应的MessageBox
。
The next bit of code we have to cover is the local tasks we can create, which we do whenever the "Run Task" Button
is clicked:
我们需要介绍的下一部分代码是我们可以创建的本地任务,只要单击“ 运行任务 ” Button
,我们便会执行此操作:
void RunTaskButton_Click(object sender, EventArgs e)
{
var id = TaskPanel.Controls.Count + 1;
var wpc = new WorkerProgressControl(id);
TaskPanel.SuspendLayout();
TaskPanel.Controls.Add(wpc);
wpc.Dock = DockStyle.Top;
TaskPanel.ResumeLayout(true);
Task.Run(() => {
for (var i = 0; i <= 100; ++i)
{
_msgWnd.PostMessage(MSG_PROGRESS, new IntPtr(id), new IntPtr(i));
Thread.Sleep(50);
}
});
}
Here, he creates a new WorkerProgressControl
which just has a Label
, a ProgressBar
, and a Value
. We give at an id
in the constructor, and then we dock it to the top of our autoscrolling tasks Panel
to create a quick and dirty tasks list. Finally we Run()
a Task
that does some faux "work" which is what that loop and everything inside it does. Note we're reporting progress using _msgWind.PostMessage()
where the first parameter is our task id
so we know which WorkerProgressControl
to update, and the second parameter is the progress value.
在这里,他创建了一个新的WorkerProgressControl
,它仅具有Label
, ProgressBar
和Value
。 我们在构造函数中给出一个id
,然后将其停靠在自动滚动任务Panel
的顶部,以创建一个快速且肮脏的任务列表。 最终,我们Run()
一个执行一些人造“工作”的Task
,这是该循环及其内部所做的所有事情。 请注意,我们使用_msgWind.PostMessage()
报告进度,其中第一个参数是我们的任务id
因此我们知道要更新哪个WorkerProgressControl
,第二个参数是进度值。
Windows uses its window message queues to notify a UI of events like mouse clicks and to create thread safe interaction between tasks or IPC between processes. That was a lot of ground to cover, but hopefully now you have a greater understanding of how this works. That's all she wrote!
Windows使用其窗口消息队列来通知UI鼠标单击之类的事件,并在任务之间或进程之间的IPC之间创建线程安全的交互。 有很多方面需要介绍,但是希望现在您对它的工作原理有了更深入的了解。 她写的就是这些!
免责声明 (Disclaimer)
The code in this article is not intended for production use. Under normal circumstances, I don't recommend using the windows message queues in .NET. There are other ways to remote, and do message passing and thread synchronization that are cross platform, more capable, and more in line with the way we do things in .NET. This was for demonstration purposes to give you a better understanding of Windows.
本文中的代码不适用于生产用途。 通常情况下,我不建议在.NET中使用Windows消息队列。 还有其他跨平台的远程方法,消息传递和线程同步方法,它们跨平台,功能更强大,并且更符合我们在.NET中的处理方式。 这是出于演示目的,目的是使您更好地了解Windows。
翻译自: https://www.codeproject.com/Articles/5274425/Understanding-Windows-Message-Queues-for-the-Cshar
c# 队列和消息队列