Tuesday, December 26, 2006

 

ProcessThread and Thread in C#

稍微整理了一下以前的一些相关笔记

1.
首先看看MSDN的标准注释

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/aptnthrd_8po3.asp

A process is a collection of virtual memory space, code, data, and system resources. A thread is code that is to be serially executed within a process. A processor executes threads, not processes, so each 32-bit application has at least one process, and a process always has at least one thread of execution, known as the primary thread. A process can have multiple threads in addition to the primary thread. Prior to the introduction of multiple threads of execution, applications were all designed to run on a single thread of execution.

Processes communicate with one another through messages, using Microsoft's Remote Procedure Call (RPC) technology to pass information to one another. There is no difference to the caller between a call coming from a process on a remote machine and a call coming from another process on the same machine.

When a thread begins to execute, it continues until it is killed or until it is interrupted by a thread with higher priority (by a user action or the kernel's thread scheduler). Each thread can run separate sections of code, or multiple threads can execute the same section of code. Threads executing the same block of code maintain separate stacks. Each thread in a process shares that process's global variables and resources.

The thread scheduler determines when and how often to execute a thread, according to a combination of the process's priority class attribute and the thread's base priority. You set a process's priority class attribute by calling the Win32® function SetPriorityClass, and you set a thread's base priority with a call to SetThreadPriority.

Multithreaded applications must avoid two threading problems: deadlocks and races. A deadlock occurs when each thread is waiting for the other to do something. The COM call control helps prevent deadlocks in calls between objects. A race condition occurs when one thread finishes before another on which it depends, causing the former to use a bogus value because the latter has not yet supplied a valid one. COM supplies some functions specifically designed to help avoid race conditions in out-of-process servers. (See Out-of-Process Server Implementation Helpers.)

另外

In most multithreading operating systems, a process gets its own memory address space; a thread doesn't. Threads typically share the heap belonging to their parent process. For instance, a JVM runs in a single process in the host O/S. Threads in the JVM share the heap belonging to that process; that's why several threads may access the same object. Typically, even though they share a common heap, threads have their own stack space. This is how one thread's invocation of a method is kept separate from another's. This is all a gross oversimplification, but it's accurate enough at a high level. Lots of details differ between operating systems.

2.
好,现在是我们讨论的问题,如何计算Process和Thread所花费的时间(主要是比较算法的优劣用)

C#中的Process属于namespace System.Diagnostics,从rotor的代码来看,它就是系统process的一个简单薄层封装。
http://www.123aspx.com/rotor/RotorSrc.aspx?rot=41538

所以我们可以直接用UserProcessorTime和PrivilegedProcessorTime两个属性来检测Process所花时间
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdiagnosticsprocessclassprivilegedprocessortimetopic.asp

其实也就是系统的GetProcessTimes函数的应用,MFC可参见
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/getprocesstimes.asp
http://www.codeproject.com/threads/getprocesstimes.asp

C#中的ProcessThread属于namespace System.Diagnostics,不过没有代码可看,估计也是一个系统thread的简单薄层封装。所以我们还有如下的属性可以使用

System.Diagnostics.ProcessThread.PrivilegedProcessorTime
System.Diagnostics.ProcessThread.TotalProcessorTime
System.Diagnostics.ProcessThread.UserProcessorTime
参见
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemDiagnosticsProcessThreadPropertiesTopic.asp

其实也就是系统的thread的GetThreadTimes函数,MFC应用参见
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/getthreadtimes.asp

可是我们一般都是从namespace System.Threading派生thread来用,这个thread是DotNet新创建的类,完全没有任何与计时有关的东西,所以不能通过比较每个thread的实际花费时间来分析算法的瓶颈。

那么虚拟机到底是怎么调度threadpool里面的thread的,难道它能不考虑每个thread花费的时间吗?我在网上翻到Chris Brumme大牛的答案是这样的

Threads, fibers, stacks & address space
by Chris Brumme
from http://blogs.msdn.com/cbrumme/archive/2003/04/15/51351.aspx

Every so often, someone tries to navigate from a managed System.Threading.Thread object to the corresponding ThreadId used by the operating system.

System.Diagnostic.ProcessThread exposes the Windows notion of threads. In other words, the OS threads active in the OS process.

System.Threading.Thread exposes the CLR’s notion of threads. These are logical managed threads, which may not have a strict correspondence to the OS threads. For example, if you create a new managed thread but don’t start it, there is no OS thread corresponding to it. The same is true if the thread stops running – the managed object might be GC-reachable, but the OS thread is long gone. Along the same lines, an OS thread might not have executed any managed code yet. When this is the case, there is no corresponding managed Thread object.

A more serious mismatch between OS threads and managed threads occurs when the CLR is driven by a host which handles threading explicitly. Even in V1 of the CLR, our hosting interfaces reveal primitive support for fiber scheduling. Specifically, look at ICorRuntimeHost’s LogicalThreadState methods. But please don’t use those APIs – it turns out that they are inadequate for industrial-strength fiber support. We’re working to get them where they need to be.

In a future CLR, a host will be able to drive us to map managed threads to host fibers, rather than to OS threads. The CLR cooperates with the host’s fiber scheduler in such a way that many managed threads are multiplexed to a single OS thread, and so that the OS thread chosen for a particular managed thread may change over time.

When your managed code executes in such an environment, you will be glad that you didn’t confuse the notions of managed thread and OS thread.

When you are running on Windows, one key to good performance is to minimize the number of OS threads. Ideally, the number of OS threads is the same as the number of CPUs – or a small multiple thereof. But you may have to turn your application design on its head to achieve this. It’s so much more convenient to have a large number of (logical) threads, so you can keep the state associated with each task on a stack.

When faced with this dilemma, developers sometimes pick fibers as the solution. They can keep a large number of cooperatively scheduled light-weight fibers around, matching the number of server requests in flight. But at any one time only a small number of these fibers are actively scheduled on OS threads, so Windows can still perform well.

SQL Server supports fibers for this very reason.

However, it's hard to imagine that fibers are worth the incredible pain in any but the most extreme cases. If you already have a fiber-based system that wants to run managed code, or if you’re like SQL Server and must squeeze that last 10% from a machine with lots of CPUs, then the hosting interfaces will give you a way to do this. But if you are thinking of switching to fibers because you want lots of threads in your process, the work involved is enormous and the gain is slight.

Instead, consider techniques where you might keep most of your threads blocked. You can release some of those threads based on CPU utilization dropping, and then use various application-specific techniques to get them to re-block if you find you have released too many. This kind of approach avoids the rocket science of non-preemptive scheduling, while still allowing you to have a larger number of threads than could otherwise be efficiently scheduled by the OS.

Of course, the very best approach is to just have fewer threads. If you schedule your work against the thread pool, we'll try to achieve this on your behalf. Our threadpool will pay attention to CPU utilization, managed blocking, garbage collections, queue lengths and other factors – then make sensible dynamic decisions about how many work items to execute concurrently. If that’s what you need, stay away from fibers.

If you have lots of threads or fibers, you may have to reduce your default stack size. On Windows, applications get 2 GB of address space. With a default stack size of 1 MB, you will run out of user address space just before 2000 threads. Clearly that’s an absurd number of threads. But it’s still the case that with a high number of threads, address space can quickly become a scarce resource.

On old versions of Windows, you controlled the stack sizes of all the threads in a process by bashing a value in the executable image. Starting with Windows XP and Windows Server 2003, you can control it on a per-thread basis. However, this isn’t exposed directly because:

1) It is a recent addition to Windows.

2) It’s not a high priority for non-EXE’s to control their stack reservation, since there are generally few threads and lots of address space.

3) There is a work-around.

The work-around is to PInvoke to CreateThread, passing a Delegate to a managed method as your LPTHREAD_START_ROUTINE. Be sure to specify STACK_SIZE_PARAM_IS_A_RESERVATION in the CreationFlags. This is clumsy compared to calling Thread.Start(), but it works.

Incidentally, there’s another way to deal with the scarce resource of 2 GB of user address space per process. You can boot the operating system with the /3GB switch and – starting with the version of the CLR we just released – any managed processes marked with IMAGE_FILE_LARGE_ADDRESS_AWARE can now take advantage of the increased user address space. Be aware that stealing all that address space from the kernel carries some real costs. You shouldn’t be running your process with 3 GB of user space unless you really need to.

The one piece of guidance from all of the above is to reduce the number of threads in your process by leveraging the threadpool. Even client applications should consider this, so they can work well in Terminal Server scenarios where a single machine supports many attached clients.

flier说得很对,Chris Brumme的blog是不能不看的。

3.
BackgroundWorker

from http://www.cnblogs.com/yizhu2000/archive/2007/10/19/929930.html

这篇我们来介绍一下异步编程的经典模式和微软对其的实现

微软推荐的异步操作模型是事件模型,也即用子线程通过事件来通知调用者自己的工作状态,也就是设计模式中的observer模式,也可以看成是上文中线程类的扩展,最后实现后调用效果类似于

MyThread thread=new MyThread()

thread.Work+=new ThreadWork(Calculate)

thread.WorkComplete+=new WorkComplete(DisplayResult)

Calculate(object sender, EventArgs e)){

....

}

DisplayResult(object sender, EventArgs e)){

...

}

这个话题已经有许多很好的文章,大家参考http://www.cnblogs.com/net66/archive/2005/08/03/206132.html,其作者在文章后附加有示例项目,项目中的线程类实现了事件发送,线程终止,报告任务进度等一系列必要的功能,大家可以自己去查看代码,我就不赘述了,我主要谈微软对这个模式的实现BackgroundWorker

上篇文章里说到了控制权的问题,上面的模型在winform下使用有个问题就是执行上下文的问题,在回调函数中(比如<例一>中的DisplayResult中),我们不得不使用BeginInvoke,才能调用ui线程创建的控件的属性和方法,

比如在上面net66的例子里

//创建线程对象
_Task = new newasynchui();
//挂接进度条修改事件
_Task.TaskProgressChanged += new TaskEventHandler( OnTaskProgressChanged1 );

//在UI线程,负责更新进度条
private void OnTaskProgressChanged1( object sender,TaskEventArgs e )
{
if (InvokeRequired ) //不在UI线程上,异步调用
{
TaskEventHandler TPChanged1 = new TaskEventHandler( OnTaskProgressChanged1 );
this.BeginInvoke(TPChanged1,new object[] {sender,e});
Console.WriteLine("InvokeRequired=true");
}
else
{
progressBar.Value = e.Progress;
}
}

可以看到,在函数里面用到了

if(InvokeRequired)

{...BeginInvoke....}

else

{....}

这个模式来保证方法在多线程和单线程下都可以运行,所以线程逻辑和界面逻辑混合在了一起,以至把以前很简单的只需要一句话的任务:progressBar.Value = e.Progress;搞的很复杂,如果线程类作为公共库来提供,对编写事件的人要求会相对较高,那么有什么更好的办法呢?

其实在.Net2.0中微软自己实现这个模式,制作了Backgroundworker这个类,他可以解决上面这些问题,我们先来看看他的使用方法

System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();

//定义需要在子线程中干的事情
bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);

//定义执行完毕后需要做的事情
bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

//开始执行
bw.RunWorkerAsync();

static void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Complete"+Thread.CurrentThread.ManagedThreadId.ToString());
}

static void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
MessageBox.Show(Thread.CurrentThread.ManagedThreadId);
}

注意我在两个函数中输出了当前线程的ID,当我们在WindowsForm程序中执行上述代码时,我们惊奇的发现,bw_RunWorkerCompleted这个回调函数居然是运行在UI线程中的,也就是说在这个方法中我们不用再使用Invoke和BeginInvoke调用winform中的控件了, 更让我奇怪的是,如果是在ConsoleApplication中同样运行这段代码,那么bw_RunWorkerCompleted输出的线程id和主线程id就并不相同.

那么BackgroundWorker到底是怎么实现跨线程封送的呢?

阅读一下这个类的代码,我们发现他借助了AsyncOperation.Post(SendOrPostCallback d, object arg)

在winform下使用这个函数,就可以使得由SendOrPostCallback定义被封送会UI线程,聪明的博友可以用这个方法来实现自己的BackgroundWorker.

继续查看下去,发现关键在于AsyncOperation的syncContext字段,这是一个SynchronizationContext类型的对象,而这个对象的Post方法具体实现了封送,当我继续查看

SynchronizationContext.Post方法时,里面简单的令人难以执行

public virtual void Post(SendOrPostCallback d, object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);
}

这是怎么回事情呢,线程池本省并不具备线程封送的能力啊

联想到在Winform程序和Console程序下程序的行为是不同的,而且SynchronizationContext的Post方法是一个virtual方法,我猜测这个方法可能被继承自他的类重写了

查询Msdn,果然发现在这个类有两个子类,其中一个就是WindowsFormsSynchronizationContext,我们来看看这个类的Post方法

public override void Post(SendOrPostCallback d, object state)
{
if (this.controlToSendTo != null)
{
this.controlToSendTo.BeginInvoke(d, new object[] { state });
}
}

哈哈,又是熟悉的beginInvoke,原来控制台程序和Winform程序加载的SynchronizationContext是不同的,所以行为才有所不同,通过简单的测试,我们可以看到控制台程序直接使用基类(SynchronizationContext),而winform程序使用这个WindowsFormsSynchronizationContext的Post方法把方法调用封送到控件的线程.

总结: 同事这个类还提供了进度改变事件,允许用户终止线程,功能全面,内部使用了线程池,能在一定成都上避免了大量线程的资源耗用问题,并通过SynchronizationContext解决了封送的问题,让我们的回调事件代码逻辑简单清晰,推荐大家使用

4.
WinForms UI Thread Invokes: An In-Depth Review of Invoke/BeginInvoke/InvokeRequred

By Justin Rogers
From http://weblogs.asp.net/justin_rogers/articles/126345.aspx

Abstract:Marshalling the execution of your code onto the UI thread in the Windows Forms environment is critical to prevent cross-thread usage of UI code. Most people don't understand how or when they'll need to use the marshalling behavior or under what circumstances it is required and when it is not. Other users don't understand what happens when you use the marshalling behavior but it isn't needed. In actuality it has no negative effects on stability, and instead reserves any negative side effects to performance only.
Understanding the semantics of when your callback methods will be called, in what order, and how might be very important to your application. In addition to the default marhalling behavior, I'll be covering special considerations for enhancing the marhsalling behavior once we fully understand how it works. We'll also cover all of the normal scenarios and uses for code execution marhsalling to make this a complete Windows Forms marshalling document.
TOC:
UCS 1: Using InvokeRequired and Invoke for Synchronous Marshalling, the default scenario
UCS 2: Using BeginInvoke for Asynchronous Marshalling
InvokeRequired and how it works
Invoke operation on the UI thread and from a different thread
InvokeMarshaledCallbacks and how it handles the callback queue
BeginInvoke operation on the UI thread and from a different thread
UCS 3: Using BeginInvoke to change a property after other events are processed, and why it can fail
Public and Internal Methods covered with a short description of what they do
Conclusion
1. UCS 1: Using InvokeRequired and Invoke for Synchronous Marshalling, the default scenarioI call this the default scenario, because it identifies the most prominent use of UI thread marshalling. In this scenario the user is either on the UI thread or they are not, and most likely they aren't sure. This can occur when you use common helper methods for acting on the UI that are called from your main code (most likely on the UI thread), and in code running on worker threads.
You can always tell if an Invoke is going to be required by calling InvokeRequired. This method finds the thread the control's handle was created on and compares it to the current thread. In doing so it can tell you whether or not you'll need to marshal. This is extremely easy to use since it is a basic property on Control. Just be aware that there is some work going on inside the method and it should have possibly been made a method instead.
Button b = new Button(); // Creates button on the current threadif ( b.InvokeRequired ) { // This shouldn't happen since we are on the same thread }else { // We should fall into here }
If your code is running on a thread that the control was not created on then InvokeRequired will return true. In this case you should either call Invoke or BeginInvoke on the control before you execute any code. Invoke can either be called with just a delegate, or you can specify arguments in the form of an object[]. This part can be confusing for a lot of users, because they don't know what they should pass to the Invoke method in order to get their code to run. For instance, let's say you are trying to do something simple, like call a method like Focus(). Well, you could write a method that calls Focus() and then pass that to Invoke.
myControl.Invoke(new MethodInvoker(myControl.Hide());
Noticed I used MethodInvoker. This is a special delegate that takes no parameters so it can be used to call any methods that take 0 parameters. In this case Focus() takes no arguments so things work. I'm telling the control to invoke the method right off of myControl, so I don't need any additional information. What happens if you need to call a bunch of methods on myControl? In that case you'll need to define a method that contains all of the code you need run and then Invoke it.
private void BunchOfCode() { myControl.Focus(); myControl.SomethingElse();}myControl.Invoke(new MethodInvoker(this.BunchOfCode());
This solves one problem, but leaves another. We just wrote code that only works only for myControl because we hard coded the control instance into our method. We can overcome this by using an EventHandler syntax instead. We'll cover the semantics of this later, so I'll just write some code that works now.
private void BunchOfCode(object sender, EventArgs e) { Control c = sender as Control; if ( c != null ) { c.Focus(); c.SomethingElse(); }}myControl.Invoke(new EventHandler(BunchOfCode));
EventArgs is always going to be empty, while sender will always be the control that Invoke was called on. There is also a generic helper method syntax you can use to circumvent any of these issues that makes use of InvokeRequired. I'll give you a version of that works with MethodInvoker and one that works with EventHandler for completeness.
private void DoFocusAndStuff() { if ( myControl.InvokeRequired ) { myControl.Invoke(new MethodInvoker(this.DoFocusAndStuff)); } else { myControl.Focus(); myControl.SomethingElse(); }}private void DoFocusAndStuffGeneric(object sender, EventArgs e) { Control c = sender as Control; if ( c != null ) { if ( c.InvokeRequired ) { c.Invoke(new EventHandler(this.DoFocusAndStuffGeneric)); } else { c.Focus(); c.SomethingElse(); } }}
Once you've set up these helper functions, you can just call them and they handle cross thread marshalling for you if needed. Notice how each method simply calls back into itself as the target of the Invoke call. This lets you put all of the code in a single place. This is a great abstraction that you can add to your application to automatically handle marshalling for you. We haven't yet had to define any new delegates to handle strange method signatures, so these techniques have low impact on the complexity of your code. I'll wrap up the Invoke use case scenario there and move into the BeginInvoke scenario.
2. UCS 2: Using BeginInvoke for Asynchronous MarshallingWhenever you call Invoke, you have to wait for the return call, so your current thread hangs until the remote operation completes. This can take some time since lots of things need to happen in order to schedule your code on the UI thread and have it execute. While you don't really have to worry that an Invoke might block indefinitely, you still can't determine exactly how long it will take (unless it really wasn't required in the first place, but we'll get to that later). In these cases you'll want to call Invoke asynchronously.
Calling your code asynchronously is simliar to calling it through Invoke. The only difference is that BeginInvoke will return immediately. You can always check for the results of your operation by calling EndInvoke, but you don't have to. In general, you'll almost never use EndInvoke unless you actually want the return value from the method which is fairly rare. The same plumbing is in the back-end for BeginInvoke as for Invoke so all we'll be doing is changing our code from UCS 1 to use BeginInvoke.
private void DoFocusAndStuff() { if ( myControl.InvokeRequired ) { myControl.BeginInvoke(new MethodInvoker(this.DoFocusAndStuff)); } else { myControl.Focus(); myControl.SomethingElse(); }}private void DoFocusAndStuffGeneric(object sender, EventArgs e) { Control c = sender as Control; if ( c != null ) { if ( c.InvokeRequired ) { c.BeginInvoke(new EventHandler(this.DoFocusAndStuffGeneric)); } else { c.Focus(); c.SomethingElse(); } }}
What happens if you do need the return value? Well, then the use case changes quite a bit. You'll need to wait until the IAsyncResult has been signalled complete and then call EndInvoke on this object to get your value. The following code will will grab the return value and then immediately call EndInvoke. Note that since the result is probably not ready yet, EndInvoke will hang. Using this combination of BeginInvoke/EndInvoke is the same as just calling Invoke.
IAsyncResult result = myControl.BeginInvoke(new MethodInvoker(myControl.Hide());myControl.EndInvoke(result);
So we'll change our behavior to check for completion status. We'll need to find some way to poll the completion status value so we don't hang our current thread and can continue doing work while we wait. Normally you'll just put places in your code to check the result status and return. We don't have the time nor space to make up such an elaborate sample here, so we'll just pretend we are doing work.
IAsyncResult result = myControl.BeginInvoke(new MethodInvoker(myControl.Hide());while ( !result.IsCompleted ) { // Do work somehow }myControl.EndInvoke(result);
The BeginInvoke use case scenario isn't much different from the Invoke scenario. The underlying reason behind using one over the other is simply how long you are willing to wait for the result. There is also the matter of whether you want the code to execute now or later. You see, if you are on the UI thread already and issue an Invoke the code runs immediately. If you instead issue a BeginInvoke you can continue executing your own code, and then only during the next set of activity on the message pump will the code be run. If you have some work to finish up before you yield execution then BeginInvoke is the answer for you.
You have to be careful when using BeginInvoke because you never know when your code will execute. The only thing you are assured is that your code will be placed on the queue and executed in the order it was placed there. This is the same guarantee you get for Invoke as well, though Invoke places your code on the queue and then exhausts it (running any queued operations). We'll examine this in more detail in later sections. For now, let's take a hard look at InvokeRequired.
3. InvokeRequired and how it worksThis is a read-only property that does quite a bit of work. You could say it ran in determinate time in most cases, but there are degenerate cases where it can take much longer. In fact the only time it is determinate is if IsHandleCreated is true meaning the control you are using is fully instantiated and has a windows handle associated with it.
If the handle is created then control falls into the check logic to see if the windows thread process id is the same as the current thread id. They use GetWindowThreadProcessID, a Win32 API call, to check the handle and find it's thread and process ID (note the process ID doesn't appear to be used). Then they grab the current thread ID through none other than GetCurrentThreadID. The result of InvokeRequired is nothing more than (threadID != currentThreadID). Pretty basic eh?
Things get more difficult when your control's handle is not created yet. In this case they have to find what they call a marshalling control for your control. This process can take some time. They walk the entire control hiearchy trying to find out if any of your parent control's have been instantiated yet and have a valid handle. Normally they'll find one. As soon as they do they fall out and return that control as your marshalling control. If they can't find any the have a fallback step. They get the parking window. They make one of these parking windows on every thread that has a message pump apparently, so no matter where you create your controls (no matter what thread) there should be at least one control that can be used as the marshalling control (unless maybe you are running in the designer ;-).
Application.GetParkingWindow is nasty. After all, this is the final fallback and the last ditch effort to find some control that can accept your windows message. The funny thing here is that GetParkingWindow is extremely determinant if your control is already created. They have some code that basically gets the ThreadContext given the thread ID of your control. That is what we've been looking for this entire time, so that code-path must be used somewhere else (darn IL is getting muddied, thank god these are small methods).
Then they start doing the magic. They assume the control is on the current thread. This is just an assumption, and it might not be true, but they make it for the sake of running the method. They get the parking window off of this current TheadContext and return that. If it hasn't been created yet, we are really screwed because that was our last chance to find a marshalling control. At this point, if we still don't have a marshalling control, they return the original control you passed in.
At the end of this entire process, if we find a marshalling control, that is used with GetWindowThreadProcessID. If not, we simply return false, indicating that an Invoke is not required. This is important. It basically means if the handle isn't created, it doesn't matter WHAT thread you are on when you call into the control. Reason being, is that there isn't any Handle, which means no real control exists yet, and all of the method calls will probably fail anyway (some won't, but those that require a HWND or Windows Handle will). This also means you don't always have to call control methods on the UI thread, only those that aren't thread safe. With InvokeRequired to the side, it is time to talk about Invoke and what it goes through.
4. Invoke operation on the UI thread and from a different threadTime to examine the Invoke operation and what is involed. To start with, we'll examine what happens when the Invoke operation is happening on the same thread as the UI thread for the control. This is a special case, since it means we don't have to marshal across a thread boundary in order to call the delegate in question.
All of the real work happens in MarshaledInvoke. This call is made on the marshalling control, so the first step is to get the marshaling control through FindMarshalingControl. The first Invoke method, without arguments, calls the Invoke method with a null argument set. The overriden Invoke in turn calls MarshaledInvoke on the marshaling control passing in the current caller (note we need this because the marshalling control might be different from the control we called Invoke on), the delegate we are marshalling, the arguments, and whether or not we want synchronous marshaling. That second parameter is there so we can use the same method for asynchronous invokes later.
// The method looks something like this and it is where all of the action occursobject MarshaledInvoke(Control invokeControl, Delegate delegate, object[] arguments, bool isSynchronous);
If the handle on the marhaling control is invalid, you get the classic exception telling you the handle isn't created and that the Invoke or what not failed. There is also some gook about ActiveX controls in there that I don't quite understand, but they appear to be demanding some permissions. Then comes the important part for calling Invoke on the UI thread. They again check the handle's thread id against the current thread id, and if we are running synchronously, they set a special bool indicating we are running synchronously and are operating on the same thread. This is the short-circuit code that gets run only when you call Invoke and are on the same thread.
Since the special case is enabled, we'll immediately call the InvokeMarshaledCallbacks method rather than posting a message to the queue. Note all other entries into this method, and all other conditions will cause a windows message to be posted and InvokeMarshaledCallbacks will later be called from the WndProc of the control once the message is received.
There is some more code before this point. Basically, they make a copy of the arguments you pass in. This is pretty smart, since I'm guessing you could try changing the arguments in the original array and thus the arguments to your delegate if they didn't make the copy. It also means, once Invoke or BeginInvoke is called, you can change your object array of parameters, aka you can reuse the array, which is pretty nice for some scenarios.
After they copy your parameters into a newly allocated array they take the liberty of grabbing the current stack so they can reattach it to the UI thread. This is for security purposes so you can't try to Invoke code on the UI thread that you wouldn't have been able to run on your own thread. They use CompressedStack for this operation and the GetCompressedStack method. While this is a public class inside of mscorlib.dll, there is NO documentation for it. It seems to me that this might be a very interesting security mechanism for API developers, but they don't give you any info on it. Maybe I'll write something about how to use it later.
With this in place, they construct a new ThreadMethodEntry. These guys are the work horse. They get queued into a collection, and are later used to execute your delegate. It appears the only additional parameter used to create this class over calling MarshaledInvoke is the CompressedStack. They also used the copied arguments array instead of the original.
They then grab the queue for these guys off of the property bag. You could never do this yourself, because they index the properties collection using object instances that you can't get access to. This is a very interesting concept, to create an object used to index a hashtable or other collection that nobody else has access to. They store all of the WinForms properties this way, as well as the events.
Finally, they queue the ThreadMethodEntry onto the queue and continue. They appear to do a bunch of locking to make all of this thread-safe. While the Invoke structure is a pain in the rear, I'm glad they reserve all of this locking to a few select methods that handle all of the thread safe operations.
Since this is an Invoke there is additional code required to make sure the operation happens synchronously. The ThreadMethodEntry implements IAsyncResult directly, so on Invoke calls, we check to make sure it isn't already completed (a call to IsCompleted), and if it isn't, we grab the AsyncWaitHandle and do a WaitOne call. This will block our thread until the operation completes and we can return our value. Why did we make a call to IsCompleted first? Well, remember that call we made to InvokeMarshaledCallbacks? Well, when we do that our operation will already be complete once we get to that portion of the code. If we didn't make this check and instead just started a WaitOne on the handle, we'd hang indefinitely.
Once the operation either completes or was already completed, we look for any exceptions. If there are exceptions, we throw them. Here have some exceptions they say ;-) If no exceptions were thrown then we return a special return value property stored on the ThreadMethodEntry. This value is set in InvokeMarshaledCallbacks when we invoke the delegate.
If you are running off the UI thread, how do things change? Well, we don't have the special same thread operation involved this time, so instead we post a message to the marshaling control. This is a special message that is constructed using some internal properties and then registered using RegisterWindowMessage. This ensures that all controls will use the same message for this callback preventing us from register a bunch of custom windows messages.
InvokeMarshaledCallbacks is an important method since it gets called both synchronously if we are on the same thread as the UI and from the WndProc in the case we aren't. This is where all of the action of calling our delegate happens and so it is where we'll be next.
5. InvokeMarshaledCallbacks and how it handles the callback queueThis method is deep. Since it has to be thread safe, we get lots of locking (even though we should only call this method from the UI thread, we have to make sure we don't step on others that are accessing the queue to add items, while we remove them). Note that this method will continue processing the entire queue of delegates, and not just one. Calling this method is very expensive, especially if you have a large number of delegates queued up. You can start to better understand the performance possibilities of asynchronous programming and how you should avoid queuing up multiple delegates that are going to do the same thing (hum, maybe that IAsyncResult will come in handy after all ;-)
We start by grabbing the delegate queue and grabbing a start entry. Then we start up a loop to process all of the entries. Each time through the loop the current delegate entry gets updated and as soon as we run out of elements, the loop exits. If you were to start an asynchronous delegate from inside of another asynchronous delegate, you could probably hang your system because of the way this queue works. So you should be careful.
The top of the loop does work with the stack. We grab the current stack so we can restore it later, then set the compressed stack that was saved onto the ThreadMethodEntry. That'll ensure our security model is in place. Then we run the delegate. There are some defaults. For instance, if the type is MethodInvoker, we cast it and call it using a method that yields better performance. If the method is of type EventHandler, then we automatically set the parameters used to call the EventHandler. In this case the sender will be the original caller, and the EventArgs will be EventArgs.Empty. This is pretty sweet, since it simplifies calling EventHandler definitions. It also means we can't change the sender or target of an EventHandler definition, so you have to be careful.
If the delegate isn't of one of the two special types then we do a DynamicInvoke on it. This is a special method on all delegates and we simply pass in our argument array. The return value is stored on our ThreadMethodEntry and we continue. The only special case is that of an exception. If an exception is thrown, we store the exception on the ThreadMethodEntry and continue.
Exiting our delegate calling code, we reset the stack frame to the saved stack frame. We then call Complete on our ThreadMethodEntry to signal anybody waiting for it to finish. If we are running asynchronously and there were exceptions we call Application.OnThreadException(). You may have noticed these exceptions happening in the background when you call BeginInvoke in your application, and this is where they come from. With all of that complete, we are done. That concludes all of the code required to understand an Invoke call, but we still have some other cases for BeginInvoke, so let's look at those.
6. BeginInvoke operation on the UI thread and from a different threadHow much different is BeginInvoke from the basic Invoke paradigm? Well, not much. There are only a couple of notes, so I don't take a bunch of your time redefining all of the logic we already discussed. The first change is how we call MarshaledInvoke. Instead of specifying true for running synchronously we instead specify false. There is also no special case for running synchronously on the UI thread, instead we always post a message to the windows pump. Finally, rather than having synchronization code on the ThreadMethodEntry, we return it immediately as an IAsyncResult that can be used to determine when the method has completed later or with EndInvoke.
That is where all of the new logic is, EndInvoke. You see, we need additional logic for retrieving the result of the operation and making sure it is completed. EndInvoke can be a blocking operation if IsCompleted is not already true on the IAsyncResult. So basically, we do a bunch of checks to make sure the IAsyncResult passed in really is a ThreadMethodEntry. If it is, and it hasn't completed, we do the same synchronization logic we did on the Invoke version, with some small changes. First, we try to do an InvokeMarshaledCallbacks if we are on the same thread. This is similar to the same thread synchronization we did in the first case. If we aren't on the same thread, then we wait on the AsyncWaitHandle. They have some code that is dangerously close to looking like a race condition here, but I think they've properly instrumented everything to prevent that scenario.
As we fall through all of the synchronization we again check for exceptions. Just like with Invoke we throw them if we have them. A lot of people don't catch these exceptions or assume they won't happen, so a lot of asynchronous code tends to fail. Catch your exceptions people ;-) If no exceptions were thrown then we return the value from the delegate and everything is done.
You see, not many changes are required in order to implement BeginInvoke over top of the same code we used in Invoke. We've already covered the changes in InvokeMarshaledCallbacks, so we appear to be complete. Time for a sample.
7. UCS 3: Using BeginInvoke to change a property after other events are processed, and why it can failSometimes events in Windows Forms can transpire against you. The classic example I use to explain this process is the AfterNodeSelect event of the TreeView control. I generally use this event in order to update a ListBox or other control somewhere on the form, and often you want to transfer focus to a new control, probably the ListBox. If you try to set the Focus within the event handler, then later on when the TreeView gets control back after the event, it sets the Focus right back to itself. You feel like nothing happened, even though it did.
You can easily fix this by using a BeginInvoke to set focus instead. We'll call Focus directly so we need to define a new delegate. We'll call it a BoolMethodInvoker since Focus() returns a bool, we can't just use the basic MethodInvoker delegate (what a shame eh?)
// Declare the delegate outside of your class or as a nested class memberprivate delegate bool BoolMethodInvoker();// Issue this call from your event instead of invoking it directly.listPictures.BeginInvoke(new BoolMethodInvoker(listPictures.Focus));
Now, knowing a bit about how the BeginInvoke stuff works, there is a way to screw yourself over. First, your method may get executed VERY soon. As a matter of fact, the next message on the pump might be a marshalling message, and then other messages in the pump that you wanted to go after might still be executed after you. In many cases your method calls will still generate even more messages so this can be circumvented a bit, but possibly not.
There is a second issue as well. If another code source calls an Invoke and you are on the UI thread, then your method may get processed even before the event handlers are done executing and the TreeView gets control back to make it's focus call. This is an edge case, but you can imagine you might run into scenarios where you want some asynchronous operations and some synchronous. You need to be aware than any synchronous call can possibly affect your asynchronous calls and cause them to be processed.
8. Public and Internal Methods covered with a short description of what they doThese are all of the public and internal methods that we covered and what they do. Kind of a quick reference. I'll probably find this very helpful later when I'm trying to derive some new functionality and I don't want to have to read my entire article.
InvokeRequired - Finds the most appropriate control and uses the handle of that control to get the thread id that created it. If this thread id is different than the thread id of the current thread then an invoke is required, else it is not. This method uses a number of internal methods to solve the issue of the most appropriate control.
Invoke - This method sets up a brand new synchronous marshalled delegate. The delegate is marshalled to the UI thread while your thread waits for the return value.
BeginInvoke - This method sets up a brand new asynchronous marshalled delegate. The delegate is marshalled to the UI thread while your thread continues to operate. An extended usage of this method allows you to continue working on the UI thread and then yield execution to the message pump allowing the delegate to be called.
EndInvoke - This method allows you to retrieve the return value of a delegate run by the BeginInvoke call. If the delegate hasn't returned yet, EndInvoke will hang until it does. If the delegate is alread complete, then the return value is retrieved immediately.
MarshaledInvoke - This method queues up marshaling actions for both the Invoke and BeginInvoke layers. Depending on the circumstances this method can either immediately execute the delegates (running on the same thread) or send a message into the message pump. It also handles wait actions during the Invoke process or returns an IAsyncResult for use in BeginInvoke.
InvokeMarshaledCallbacks - This method is where all of your delegates get run. This method is either called from MarshaledInvoke or WndProc depending on the circumstances. Once inside of this method, the entire queue of delegates is run through and all events are signalled allowing any blocking calls to operate (Invoke or EndInvoke calls) and setting all IAsyncResult objects to the IsCompleted = true state. This method also handles exception logic allowing exceptions to be thrown back on the original thread for Invoke calls or tossed into the applications thread exception layer if you are using BeginInvoke and were running asynchronous delegates.
FindMarshallingControl - Walks the control tree from current back up the control hierarchy until a valid control is found for purposes of finding the UI thread id. If the control hierarchy doesn't contain a control with a valid handle, then a special parking window is retrieved. This method is used by many of the other methods since a marshalling control is the first step in marshalling a delegate to the UI thread.
Application.GetParkingWindow - This method takes a control and finds the marking window for it. If the control has a valid handle then the thread id of the control is found, the ThreadContext for that thread is retreived, and the parking window is returned. If the control does not have a valid handle then the ThreadContext of the current thread is retrieved and the parking window is returned. If no context is found (really shouldn't happen) null is returned.
ThreadContext.FromId - This method takes a thread id and indexes a special hash to find the context for the given thread. If one doesn't exist then a new ThreadContext is created and returned in it's place.
ThreadContext.FromCurrent - This method grabs the current ThreadContext out of thread local storage. I'm guessing this must be faster than getting the current thread id and indexing the context hash, else why would they use thread local storage at all?
ThreadContext..ctor() - This is the most confusing IL to examine, but it appears the constructor does some self registration into a context hash that the other methods use to get the context for a given thread. They wind up using some of the Thread methods, namely SetData, to register things into thread local storage. Why they use thread local storage and a context hash indexed by thread ID, I'm just not sure.
9. ConclusionYou've learned quite a bit about the Windows Forms marshalling pump today and how it handles all of the various methods of cross thread marshalling. You've also gotten a peak deeper into the Windows Forms source through a very detailed IL inspection. I've come up with some derived concepts based on this whole process, so maybe these will lead into some even more compelling articles. Even more importantly, we've learned how the process can break down if we are expecting a specific order of events.
I had never fully examined this code before, so even I was surprised at some of what I found. For instance, the performance implications of calling the same method multiple times asynchronously might be something that should be considered. Knowing that all delegates will be processed in a tight loop is pretty huge and that items can be queued while others are being dequeued (aka you can hang yourself). Finally, the realization that if you use an EventHandler type, you can't pass in the sender explicitly might lead to confusion for some folks. After all, if you mock up an arguments array and pass it to Invoke or BeginInvoke you would expect it to be used.

5.
在多线程中如何调用Winform

From http://blog.csdn.net/sangzier/archive/2004/11/01/162310.aspx

问题的产生:

  我的WinForm程序中有一个用于更新主窗口的工作线程(worker thread),但文档中却提示我不能在多线程中调用这个form(为什么?),而事实上我在调用时程序常常会崩掉。请问如何从多线程中调用form中的方法呢?

  解答:

  每一个从Control类中派生出来的WinForm类(包括Control类)都是依靠底层Windows消息和一个消息泵循环(message pump loop)来执行的。消息循环都必须有一个相对应的线程,因为发送到一个window的消息实际上只会被发送到创建该window的线程中去。其结果是,即使提供了同步(synchronization),你也无法从多线程中调用这些处理消息的方法。大多数plumbing是掩藏起来的,因为WinForm是用代理(delegate)将消息绑定到事件处理方法中的。WinForm将Windows消息转换为一个基于代理的事件,但你还是必须注意,由于最初消息循环的缘故,只有创建该form的线程才能调用其事件处理方法。如果你在你自己的线程中调用这些方法,则它们会在该线程中处理事件,而不是在指定的线程中进行处理。你可以从任何线程中调用任何不属于消息处理的方法。

  Control类(及其派生类)实现了一个定义在System.ComponentModel命名空间下的接口 -- ISynchronizeInvoke,并以此来处理多线程中调用消息处理方法的问题:

public interface ISynchronizeInvoke
{
 object Invoke(Delegate method,object[] args);
 IAsyncResult BeginInvoke(Delegate method,object[] args);
 object EndInvoke(IAsyncResult result);
 bool InvokeRequired {get;}
}

  ISynchronizeInvoke提供了一个普通的标准机制用于在其他线程的对象中进行方法调用。例如,如果一个对象实现了ISynchronizeInvoke,那么在线程T1上的客户端可以在该对象中调用ISynchronizeInvoke的Invoke()方法。Invoke()方法的实现会阻塞(block)该线程的调用,它将调用打包发送(marshal)到 T2,并在T2中执行调用,再将返回值发送会T1,然后返回到T1的客户端。Invoke()方法以一个代理来定位该方法在T2中的调用,并以一个普通的对象数组做为其参数。

  调用者还可以检查InvokeRequired属性,因为你既可以在同一线程中调用ISynchronizeInvoke也可以将它重新定位(redirect)到其他线程中去。如果InvokeRequired的返回值是false的话,则调用者可以直接调用该对象的方法。

  比如,假设你想要从另一个线程中调用某个form中的Close方法,那么你可以使用预先定义好的的MethodInvoker代理,并调用Invoke方法:

Form form;
/* obtain a reference to the form,
then: */
ISynchronizeInvoke synchronizer;
synchronizer = form;

if(synchronizer.InvokeRequired)
{
MethodInvoker invoker = new
MethodInvoker(form.Close);
synchronizer.Invoke(invoker,null);
}
else
form.Close();

  ISynchronizeInvoke不仅仅用于WinForm中。例如,一个Calculator类提供了将两个数字相加的Add()方法,它就是通过ISynchronizeInvoke来实现的。用户必须确定ISynchronizeInvoke.Invoke()方法的调用是执行在正确的线程中的。

  C# 在正确的线程中写入调用

  列表A. Calculator类的Add()方法用于将两个数字相加。如果用户直接调用Add()方法,它会在该用户的线程中执行调用,而用户可以通过ISynchronizeInvoke.Invoke()将调用写入正确的线程中。
  列表A:


public class Calculator : ISynchronizeInvoke
{
 public int Add(int arg1,int arg2)
 { 
  int threadID = Thread.CurrentThread.GetHashCode();
  Trace.WriteLine( "Calculator thread ID is " + threadID.ToString());
  return arg1 + arg2;
 }
 //ISynchronizeInvoke implementation
 public object Invoke(Delegate method,object[] args)
 {
  public IAsyncResult BeginInvoke(Delegate method,object[] args)
  {
   public object EndInvoke(IAsyncResult result)
   {
    public bool InvokeRequired
    {
    }
   }
   //Client-side code
   public delegate int AddDelegate(int arg1,int arg2);

    int threadID = Thread.CurrentThread.GetHashCode();
    Trace.WriteLine("Client thread ID is " + threadID.ToString());

    Calculator calc;
    /* Some code to initialize calc */

    AddDelegate addDelegate = new AddDelegate(calc.Add);

    object[] arr = new object[2];
    arr[0] = 3;
    arr[1] = 4;

    int sum = 0;
    sum = (int) calc.Invoke(addDelegate,arr);
    Debug.Assert(sum ==7);

    /* Possible output:
    Calculator thread ID is 29
    Client thread ID is 30
    */


  或许你并不想进行同步调用,因为它被打包发送到另一个线程中去了。你可以通过BeginInvoke()和EndInvoke()方法来实现它。你可以依照通用的.NET非同步编程模式(asynchronous programming model)来使用这些方法:用BeginInvoke()来发送调用,用EndInvoke()来实现等待或用于在完成时进行提示以及收集返回结果。

  还值得一提的是ISynchronizeInvoke方法并非安全类型。 类型不符会导致在执行时被抛出异常,而不是编译错误。所以在使用ISynchronizeInvoke时要格外注意,因为编辑器无法检查出执行错误。

  实现ISynchronizeInvoke要求你使用一个代理来在后期绑定(late binding)中动态地调用方法。每一种代理类型均提供DynamicInvoke()方法: public object DynamicInvoke(object[]
args);

  理论上来说,你必须将一个方法代理放到一个需要提供对象运行的真实的线程中去,并使Invoke() 和BeginInvoke()方法中的代理中调用DynamicInvoke()方法。ISynchronizeInvoke的实现是一个非同一般的编程技巧,本文附带的源文件中包含了一个名为Synchronizer的帮助类(helper class)和一个测试程序,这个测试程序是用来论证列表A中的Calculator类是如何用Synchronizer类来实现ISynchronizeInvoke的。Synchronizer是ISynchronizeInvoke的一个普通实现,你可以使用它的派生类或者将其本身作为一个对象来使用,并将ISynchronizeInvoke实现指派给它。

  用来实现Synchronizer的一个重要元素是使用一个名为WorkerThread的嵌套类(nested class)。WorkerThread中有一个工作项目(work item)查询。WorkItem类中包含方法代理和参数。Invoke()和BeginInvoke()用来将一个工作项目实例加入到查询里。WorkerThread新建一个.NET worker线程,它负责监测工作项目的查询任务。查询到项目之后,worker会读取它们,然后调用DynamicInvoke()方法。

6.
通过多线程为基于 .NET 的应用程序实现响应迅速的用户

http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/
softwaredev/misMultithreading.mspx

From http://www.vckbase.com/document/viewdoc/?id=1126



<< Home

This page is powered by Blogger. Isn't yours?