Wednesday, December 17, 2008

The Way of Quitting an Application

There are several ways of letting an application to quit programmatically. But you should choose the best one among them.

Environment.Exit Method

This method will force a process to terminate. So the process has no chance to do cleaning up work. No code after Environment.Exit will be executed. Any data that is unsaved will be lost. Not recommend this way.

Process.Kill Method

Killing a process just like Environment.Exit, terminates the process immediately and the process doesn't have the last chance to breath. So again, it's not recommended.

Process.CloseMainWindow Method

This method will send a request to a process' message loop, if the process does have a message loop. The process thus has a chance to do cleaning up work, and save unsaved data to disk then terminates gracefully. This method can be called either from another process or within the process that to be terminated.

Environment.Exit Method http://msdn.microsoft.com/en-us/library/system.environment.exit.aspx

Process.CloseMainWindow Method http://msdn.microsoft.com/en-us/library/system.diagnostics.process.closemainwindow.aspx

Monday, November 17, 2008

Tricks for repairing Visual Studio if it doesn't work with unit test

Sometimes, Visual Studio doesn't work with unit test. Unit test list can't be loaded when you try to load an old unit test project. Or, it can load the list, but when you try to run the unit test case, Visual Studio just tell you it is not able to run the unit test, blah blah blah...

There must be bugs in Visual Studio or in your unit project settings. Please try the following, the easiest one first:

  1. Delete .suo, .vsmdi of your solution then reload your project.
  2. Type devenv /setup or devenv /resetuserdata. But please be careful, it will clear your user setting data and Visual Studio will start from a factory state.
  3. Check if any references of your unit test project does not work. Just click on the reference, if Visual Studio will bring you to the correct position of the Object Browser, it's the correct reference. Otherwise, not. To fix the reference, first remove the reference, then add it again. If you can't find the reference, probably you have to reinstall the relevant software. The issue I met is TeamSystem.Data.UnitTesting won't work. And the attribute TestMethod is undefined because it is defined in the assembly that hasn't been referenced correctly from the unit test project.

Monday, October 27, 2008

Tricks for Writing Unit Tests for WPF Applications

Usually you write unit tests for non-UI related classes that only do background logics. But sometimes you can't avoid writing unit tests for UI related classes. These days I tried such kind of unit test. And there are some tricks that are useful while you are writing WPF unit tests I willing to share with you. Let's get started.

Get Your Window Ran

First thing first, you should get your window shown and ran. If your window is a child window of your whole application, then you have to create a fake parent window. And then build relationship between the testee window and the fake parent window.
Window fakeParentWindow = new Window();
Foo testee = new Foo();
fakeParentWindow.Content = testee; // if testee is a kind of UIElement
testee.Owner = fakeParentWindow; // if testee is a kind of Window
fakeParentWindow.Show();
testee.Show(); // if it's a Window

If the testee is a Window, you can let it be the child window of the fakeParentWindow by assign fakeParentWindow to testee's property Owner. Or just do nothing you will get a top window testee. Don't forget to call Show() if it is a Window.

That's not everything of initializing a stage for your unit test. There are at least two threads you may interested in
  • Main thread
  • The worker thread running as an agent for your unit test case (I don't know why it is called an agent thread)
As listed above, your test case will be running within a worker thread. Almost all thread time are dedicated to your test method if you don't carry out some special actions. If your test method blocks the worker thread, your windows won't get message pumped and events won't be handled, and windows even won't get rendered. To solve the problem, just call System.Windows.Threading.Dispatcher.Run() which will start pumping messages and get dispatcher works.
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Background,
new DispatcherOperationCallback(CarryOutTestSteps),
null);

System.Windows.Threading.Dispatcher.Run();

Since Dispatcher.Run() will block the test method, you have to invoke your test steps via CurrentDispatcher.BeginInvoke() in order to starting test. Your test steps will still be processed in the worker thread, but it will be processed as a dispatcher work item.

By now, your window is showing.

Don't put Dispatcher.Run() into TestInitialize(), because Dispatcher.Run() will also blocks the thread and your test method won't get the chance to run.

Quit the Unit Test

Soon you will find that the unit test won't quit even the dispatcher work item has been completed, and even you close the windows manually. The thread is still enjoying pumping messages. To tell it to stop doing stupid things, just call Dispatcher.ExitAllFrames() when you finished all test steps:
System.Windows.Threading.Dispatcher.ExitAllFrames();
Everything looks good, but... you will get an exception during the thread's termination. The exception says:

"An unhandled exception of type 'System.Runtime.InteropServices.InvalidComObjectException' occurred in PresentationCore.dll Additional information: COM object that has been separated from its underlying RCW cannot be used."

I searched on the Internet. Eventually I found that was a bug of .Net Framework. A work around came from Visual Studio Product Team is calling CurrentDispatcher.InvokeShutdown() from TestCleanup().
[TestCleanup]
public void TestCleanup()
{
System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown();
}
Be careful, .Net Framework may be changed letting this work around unnecessary.

References

  1. WPF / Visual Studio 2008 Unit Test Workaround
  2. Unit Tests Which Show Windows Cause VsTestHost/MsTest ShutDown Crashes in VS 2008 RTM

Thursday, October 16, 2008

WPF Threading Model via Windbg

In this post, I will show you how to discover the threading model of WPF via Windbg. This post is not a complete description of WPF threading model, but just listed what I've found after my efforts.

Windbg is a powerful debugger, though may be not the most powerful one. It can really help you to find root causes for some though bugs. Download Windbg here.

To dive into WPF, first, create a simple WPF application. Just use Visual Studio to create a default WPF application project. And build the application to get executable generated. Then, launch Windbg, and load it into Windbg. Don't forget to load Windbg extensions such as sos or psscor2, to help your debugging.

OK, let the application run. When you see the application window, break the application by clicking the 'break' button on the Windbg's toolbar. Let's go to thread 0 and see its stack by these two commands:

~0s
!clrstack

The first command switches current thread to 0, and the second will display CLR stack. It's very clear that thread 0 is our UI thread.

0030e824 56d5dfe0 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
0030ec5c 00a20bd3 [InlinedCallFrame: 0030ec5c] MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr, IntPtr, Int32, IntPtr, IntPtr)
0030ec58 56d5f0fe MS.Win32.HwndSubclass.DefWndProcWrapper(IntPtr, Int32, IntPtr, IntPtr)
0030ede8 00a20bd3 [NDirectMethodFrameStandalone: 0030ede8] MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr, IntPtr, Int32, IntPtr, IntPtr)
0030ee04 56d5dfe0 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
0030ef90 00a20bd3 [NDirectMethodFrameStandalone: 0030ef90] MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
0030efa0 56d61824 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
0030eff0 56d616c7 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
0030f00c 56d6162d System.Windows.Threading.Dispatcher.Run()
0030f018 5533ddb0 System.Windows.Application.RunInternal(System.Windows.Window)
0030f044 5533dbe5 System.Windows.Application.Run(System.Windows.Window)
0030f054 5533d836 System.Windows.Application.Run()
0030f05c 003500ad SimpleWPFApp.App.Main()
0030f284 79e7c74b [GCFrame: 0030f284]

You may want to try other threads, and will find they are not CLR threads. And next step, let's try to find which thread is render thread. Use command 'kb' repeatedly and you will find one thread looks interesting. It is trying to GetWork and is waiting for something.

0:005> kb
ChildEBP RetAddr Args to Child
0491fab0 77719254 7679c244 00000234 00000000 ntdll!KiFastSystemCallRet [d:\rtm\base\ntos\rtl\i386\userdisp.asm @ 645]
0491fab4 7679c244 00000234 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc [o:\rtm.obj.x86fre\base\ntdll\daytona\objfre\i386\usrstubs.asm @ 2939]
0491fb24 7679c1b2 00000234 ffffffff 00000000 KERNEL32!WaitForSingleObjectEx+0xbe [d:\rtm\base\win32\client\synch.c @ 1524]
0491fb38 72790b19 00000234 ffffffff 004dd410 KERNEL32!WaitForSingleObject+0x12 [d:\rtm\base\win32\client\synch.c @ 1429]
0491fb68 7278aadc 0491fb7c 004dd410 004dd4a0 MilCore!CPartitionManager::GetWork+0x1a5 [d:\rtm\windows\mil\core\uce\partitionmanager.cpp @ 1111]
0491fb80 7278aaa4 00000000 00000000 004dd410 MilCore!CPartitionThread::Run+0x1a [d:\rtm\windows\mil\core\uce\partitionthread.cpp @ 222]
0491fb98 76794911 004dd410 0491fbe4 776fe4b6 MilCore!CPartitionThread::ThreadMain+0x1e [d:\rtm\windows\mil\core\uce\partitionthread.cpp @ 46]
0491fba4 776fe4b6 004dd410 73edafdb 00000000 KERNEL32!BaseThreadInitThunk+0xe [d:\rtm\base\win32\client\thread.c @ 66]
0491fbe4 776fe489 7278aa86 004dd410 00000000 ntdll!__RtlUserThreadStart+0x23 [d:\rtm\base\ntos\rtl\rtlexec.c @ 2740]
0491fbfc 00000000 7278aa86 004dd410 00000000 ntdll!_RtlUserThreadStart+0x1b [d:\rtm\base\ntos\rtl\rtlexec.c @ 2672]

By checking the call stack, we find there's an argument passed to WaitForSingleObject and WaitForSingleObjectEx. The argument is 00000234. It looks like some synchronization objects. Let's check what is it:

0:000> !handle 0x234 ff
Handle 234
Type Event
Attributes 0
GrantedAccess 0x1f0003:
Delete,ReadControl,WriteDac,WriteOwner,Synch
QueryState,ModifyState
HandleCount 2
PointerCount 4
Name <none>
Object Specific Information
Event Type Auto Reset
Event is Waiting

Windbg tells us it's an event handle. Hmmm, that's reasonable. If you continue to check other thread's call stack, and you will find there's only one thread is waiting for some synchronization events. This thread must be render thread, because communication between render thread and UI thread must be based on synchronization object. So, next task is to find where the event been set. Set breakpoint on KERNEL32!SetEvent won't work for us because there are so many calls to SetEvent everywhere in the application. Windbg offers a kind of conditional breakpoint which can help us to break the application at the right position.

bp kernel32!SetEvent "j (edx=234) '.echo foo';'gc'"

Here 234 is the event handle, remember that we found this handle in render thread. You should replace it with the handle you find in your situation. Let the application go. The breakpoint is so great though, it seems that it can't be hit every time when the code goes to the right position. I don't know why. But if you try to resize the application's window repeatedly, you will have greater chance to hit the breakpoint. When the application got hit the breakpoint, you can see the call stack of thread 0 like this:

0:000> kb
ChildEBP RetAddr Args to Child
0030d95c 72748a36 00000234 004de51c 004b3a18 KERNEL32!SetEvent+0x14 [d:\rtm\base\win32\client\synch.c @ 506]
0030d978 727489d1 004b3a18 004de2f0 00000000 MilCore!CPartitionManager::ScheduleBatchProcessing+0x54 [d:\rtm\windows\mil\core\uce\partitionmanager.cpp @ 482]
0030d9d0 7274974e 0030d9ec 0030da24 004e7188 MilCore!CComposition::SubmitBatch+0x96 [d:\rtm\windows\mil\core\uce\composition.cpp @ 359]
0030d9e4 727488ba 004b3a18 0030da34 004de1d0 MilCore!CConnectionContext::SendBatchToChannel+0x30 [d:\rtm\windows\mil\core\uce\connectioncontext.cpp @ 568]
0030d9f8 72748868 00000004 004e7294 0030da3c MilCore!CConnectionContext::SendPacketToConnection+0xee [d:\rtm\windows\mil\core\uce\connectioncontext.cpp @ 450]
0030da08 727491cd 0030da24 01d47b28 00000003 MilCore!CMilCrossThreadTransport::PostPacket+0x3b [d:\rtm\windows\mil\core\uce\crossthreadtransport.cpp @ 145]
0030da3c 727491ff 004e7294 004e7208 72742c35 MilCore!CMilConnection::SubmitBatch+0xa9 [d:\rtm\windows\mil\core\uce\connection.cpp @ 619]
0030da48 72742c35 01d47b28 72742c9f 004e7208 MilCore!CMilChannel::FlushCommands+0x21 [d:\rtm\windows\mil\core\uce\clientchannel.cpp @ 721]
0030da50 72742c9f 004e7208 0030daa0 5395bc2d MilCore!CMilChannel::Commit+0x20 [d:\rtm\windows\mil\core\uce\clientchannel.cpp @ 626]
0030da5c 5395bc2d 004e7208 a1590ca7 79e73940 MilCore!MilChannel_CommitChannel+0x21 [d:\rtm\windows\mil\core\uce\apifunc.cpp @ 523]
0030daa0 539bb1b3 0000004f 00000003 00000000 PresentationCore_ni!`dynamic atexit destructor for '<CrtImplementationDetails>::AtExitLock::_lock'' <PERF> (PresentationCore_ni+0xbbc2d)
0030db10 53977fea 00000000 0000020f 0000024e PresentationCore_ni!`dynamic atexit destructor for '<CrtImplementationDetails>::AtExitLock::_lock'' <PERF> (PresentationCore_ni+0x11b1b3)
0030db98 79e75519 56d61428 00000000 01d116b0 PresentationCore_ni!`dynamic atexit destructor for '<CrtImplementationDetails>::AtExitLock::_lock'' <PERF> (PresentationCore_ni+0xd7fea)
0030db9c 56d61428 00000000 01d116b0 01d6c134 mscorwks!ThreadNative::FastGetCurrentThread+0x6 [f:\redbits\ndp\clr\src\vm\comsynchronizable.cpp @ 1004]
00000000 00000000 00000000 00000000 00000000 WindowsBase_ni!IsEncryptedPackageEnvelope+0x53 [d:\SP1\windows\wcp\Base\System\IO\Packaging\EncryptedPackage.cs @ 595]

It's interesting. Isn't it? Check the handle 234 right now and you will see the event is still in waiting state. But if you step over KERNEL32!SetEvent, you will find the event turned to 'set state':

0:000> !handle 0x234 ff
Handle 234
Type Event
Attributes 0
GrantedAccess 0x1f0003:
Delete,ReadControl,WriteDac,WriteOwner,Synch
QueryState,ModifyState
HandleCount 2
PointerCount 4
Name <none>
Object Specific Information
Event Type Auto Reset
Event is Set

From current UI thread's call stack you can find the data submission and synchronization secrete. UI thread and render thread are working in an asynchronous mode. It means that even if you completed some invocations of drawing objects, these drawing commands may be pending in the queue and won't be executed by render thread. I remember that once I am developing a program that drawing some animation, I found that even if I received the animation completion event, the animation was still going on on the screen.

Saturday, September 27, 2008

Try to use "Insert Code for Windows Live Writer" for Live Writer

If you use Live Writer to post articles onto blogspot, you now can use "Insert Code for Windows Live Writer" plug-in for Live Writer. It's more convenient comparing to web page code formatter.

    public partial class Window1 : Window
{
public class InnerObject
{
public InnerObject()
{
Debug.WriteLine("InnerObject() ctor.");
}
}

public class Foo
{
private int field = 99;
private InnerObject innerObject = new InnerObject();

public Foo()
{
}

public Foo(int a)
{
}
}

public Window1()
{
InitializeComponent();

Foo foo1 = new Foo();
Foo foo2 = new Foo(12);
}
}

Try to use Live Writer

Live writer is a cool tool for you to writer blog posts. I've never used it before. A friend of mine told me that Live Writer can do that. I was so exciting because I was searching for such kind of software for a long time.

Cool indeed!

Monday, September 1, 2008

Don't forget to call EndXxx()

Don't forget to call EndXxx() if initialized an asynchronous call via BeginXxx(), otherwise there will be resource leaks. I did forget to do this in post: [Multi-Threading] Syncing between Threads by SynchronizationContext .

EndXxx() usually should be called before you want to get the result of the asynchronous operation. You can call it right after BeginXxx(), or when you get noticed about the completion of the asynchronous operation. But remember, you should call it only for one time. If you call it the second time, the behaviour are not predicatible since resources have been released during the first call to EndXxx().

Sunday, August 31, 2008

How to Avoid Duplicated Initialization of Fields


It is said in Jeff's book that initialization work of a field together with its definition will be done in ctors. If you have several ctors, the field will be initialized in these ctors seperately. See following example:
    public partial class Window1 : Window
{
public class InnerObject
{
public InnerObject()
{
Debug.WriteLine("InnerObject() ctor.");
}
}

public class Foo
{
private int field = 99;
private InnerObject innerObject = new InnerObject();

public Foo()
{
}

public Foo(int a)
{
}
}

public Window1()
{
InitializeComponent();

Foo foo1 = new Foo();
Foo foo2 = new Foo(12);
}
}

We call these two ctors. And let's have a look at IL:
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 a) cil managed
{
// Code size 29 (0x1d)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.s 99
IL_0003: stfld int32 FieldInitialization.Window1/Foo::'field'
IL_0008: ldarg.0
IL_0009: newobj instance void FieldInitialization.Window1/InnerObject::.ctor()
IL_000e: stfld class FieldInitialization.Window1/InnerObject FieldInitialization.Window1/Foo::innerObject
IL_0013: ldarg.0
IL_0014: call instance void [mscorlib]System.Object::.ctor()
IL_0019: nop
IL_001a: nop
IL_001b: nop
IL_001c: ret
} // end of method Foo::.ctor

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 29 (0x1d)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.s 99
IL_0003: stfld int32 FieldInitialization.Window1/Foo::'field'
IL_0008: ldarg.0
IL_0009: newobj instance void FieldInitialization.Window1/InnerObject::.ctor()
IL_000e: stfld class FieldInitialization.Window1/InnerObject FieldInitialization.Window1/Foo::innerObject
IL_0013: ldarg.0
IL_0014: call instance void [mscorlib]System.Object::.ctor()
IL_0019: nop
IL_001a: nop
IL_001b: nop
IL_001c: ret
} // end of method Foo::.ctor


Very clear. Two fields, Foo.field and Foo.innerObject have initialization code both in Foo() and Foo(int a). So if you have large number of fields, many ctors, and you care about code size, don't initialize fields with their definition. Put initialization code into one ctor (usually the one don't have parameters, default ctor), and let other cotrs call it. And more, I found another easy way to do this. Just initialize fields with their definition and let other ctors call default ctor, like this:
        public class Foo
{
private int field = 99;
private InnerObject innerObject = new InnerObject();

public Foo()
{
}

public Foo(int a) : this()
{
}
}
And let look at the IL:
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 a) cil managed
{
// Code size 10 (0xa)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void FieldInitialization.Window1/Foo::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: nop
IL_0009: ret
} // end of method Foo::.ctor

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 29 (0x1d)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.s 99
IL_0003: stfld int32 FieldInitialization.Window1/Foo::'field'
IL_0008: ldarg.0
IL_0009: newobj instance void FieldInitialization.Window1/InnerObject::.ctor()
IL_000e: stfld class FieldInitialization.Window1/InnerObject FieldInitialization.Window1/Foo::innerObject
IL_0013: ldarg.0
IL_0014: call instance void [mscorlib]System.Object::.ctor()
IL_0019: nop
IL_001a: nop
IL_001b: nop
IL_001c: ret
} // end of method Foo::.ctor


As you can see initialization code in Foo(int a) is gone. C# compiler did the optimization. I think, it's the better way of initializing fields, though Jeff suggest to do initialization expilicitly in default ctor.

But, I have a question, can we rely on this optimization behavor of C# compiler?

Tuesday, August 26, 2008

Try to post source code in an elegant manner

Elegance source code via http://formatmysourcecode.blogspot.com/ :

private void HeavyBackgroundWork()
{
Debug.WriteLine("Background thread: " + Thread.CurrentThread.ManagedThreadId);

// To simulate a time-cost work.
Thread.Sleep(1000);

synchronizationContext.Post(new SendOrPostCallback(delegate(object param)
{
// We have to access TextInfo by synchronizationContext, otherwise
// you will meet an exception.
TextInfo.Text = "HeavyBackgroundWork() has been finished.";
}),
null);

Debug.WriteLine("HeavyBackgroundWork finished.");
}


Another code formatter: http://www.manoli.net/csharpformat/.
But it seems that it's not friendly with blogspot.

[Multi-Threading] Syncing between threads by SynchronizationContext

-----------------------------------------
Download source code package for this article:

If you can't see/download the zip file, please try to use TOR. TOR is a great tool that can break the GFW and let you access any website that prohibited by GFW.
http://vidalia-project.net/download.php
-----------------------------------------

I'd like to offer a series of tech articles on Multi-Threading which is a hot topic these days. Actually, I have a tech talk weeks later. So I'm trying to introduce my learning on this topic to our group first :) Please don't hesitate to give me suggestions to improve my knowledge.


Today's topic is syncing between threads by SynchronizationContext.

As you know, from MFC, WinForm to WPF, UI element can't be accessed from a thread other than UI thread. If you try to do this, you will meet an assertion, or exception. For WPF applications, there will be two threads. One is rendering thread, the other is UI thread. Rendering thread is responsible for rendering UI elements to graphics card, while UI thread is for managing UI elements, and do application logic, etc. MSDN says that you can only access UI elements from UI thread. So, how can we delegate a task which will accessing UI element from a background thread? Since we need to update UI while doing background work, reporting progress or the completion of the work. SynchronizationContext can help us.

UI thread works on a work item queue mode. All works queued in a queue. UI thread select a work item which has the highest priority in the queue when the thread finished a work item. Because work items will be executed within UI thread, a big work item can block UI operations. So make work items small enough, don't do heavy work in it. That's why we need a background worker thread.

SynchronizationContext provides Post() and Send(). You can call them from your background thread, and delegate methods passed to them will be executed within UI thread. Delegate method passed to Post() will be executed asynchronously, while to Send(), the caller of Send() will wait until the delegate method returned.

DIG DEEPER: We can imaging the internal work of Post() or Send(), they will obtain the lock of the queue (work item queue) first, then insert the work item to the queue, and then release the lock.

Please find sample code at the end of this article. Firstly, we create a background thread in Window1() which is the initialization method of the application. We use the BeginInvoke() of the delegate type HeavyBackgroundWorkDelegate. The background thread will be created and started. AsyncCallback() is the handler of completion of background thread. Please note, it will be called from the background thread. In HeavyBackgroundWork(), the entry of background thread, we access UI element by synchronizationContext.Post().

From the debug ourput of the application, we can see, AsyncCallback is in the same thread as background, while UI thread is a different thread.

-------------------
UI thread: 10
Background thread: 6
HeavyBackgroundWork finished.
AsyncCallback is called from thread: 6
----------------------


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Diagnostics;

namespace Async_SynchronizationContext
{
///
/// Interaction logic for Window1.xaml
///
public partial class Window1 : Window
{
private delegate void HeavyBackgroundWorkDelegate();
private SynchronizationContext synchronizationContext;

private void HeavyBackgroundWork()
{
Debug.WriteLine("Background thread: " + Thread.CurrentThread.ManagedThreadId);

// To simulate a time-cost work.
Thread.Sleep(1000);

synchronizationContext.Post(new SendOrPostCallback(delegate(object param)
{
// We have to access TextInfo by synchronizationContext, otherwise
// you will meet an exception.
TextInfo.Text = "HeavyBackgroundWork() has been finished.";
}),
null);

Debug.WriteLine("HeavyBackgroundWork finished.");
}

public void AsyncCallback(IAsyncResult ar)
{
// This method will be called from HeavyBackgroundWork thread, not UI thread.
Debug.WriteLine("AsyncCallback is called from thread: " + Thread.CurrentThread.ManagedThreadId);
}

public Window1()
{
InitializeComponent();

synchronizationContext = SynchronizationContext.Current;

Debug.WriteLine("UI thread: " + Thread.CurrentThread.ManagedThreadId);
HeavyBackgroundWorkDelegate backgroundWork = new HeavyBackgroundWorkDelegate(HeavyBackgroundWork);
backgroundWork.BeginInvoke(AsyncCallback, null);
}
}
}

Monday, August 11, 2008

My public key

If you have something secrete for me, please use this key:


pub 1024D/01531EA3 2/27/2007 Shaking Sun (Sun Shaking GENUINE)
Primary key fingerprint: 9C22 753B 4BB0 1740 91F7 D933 BCB3 C63A 0153 1EA3

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.7 (MingW32) - WinPT 1.2.0

mQGiBEXjoIkRBACjQiSDX9+YKW+p2jMAF1aZmFyFU67JKJ1Q1jMnZ8yTyK0iioYL
AUsW1Gye18yn5QLlyI38KN6mtO9Zb9SElyGsBjx0gfiarD1E8pHK7whFq+ZrY5WD
wdYsnXOKtR1LE/triPCtypBZRTGZqqJnAjlycopIYX2esnj+Q7U42JPP/wCg3Bm6
B2iSJLC/NolfvkyqsuNyObED/3FSXlz87V5Ve+A53G2jzHD3xb0fFyVvrCAlVcfp
w3jqRCDpqNnhEWfRF82eymhENSDJ5LLaXGvXk/FP0i6delbzoqyyZaTa8MieI9Gi
QwtZwqH/5VtxR859qaITfEeigYaEbQYg5uQLPef26ginwdu3amKWWEt33pla8ojG
crcBBACU3YfwJNttscCNQqMt0SgKdPrYL9/9/7j7cIpxhG/Tcx1WW/QV7/5O9GoH
iwN5T77ZbQen1SIVwbpr9B8fUgJ0ZiDZ//o4YaAmrd9u5ld1EnL2wpNVo2rBNHi/
BmBy6CNiGf4BTiJChPQxKm2QXjr7FY67w6eseY5oJDVDlIAyIrQ4U2hha2luZyBT
dW4gKFN1biBTaGFraW5nIEdFTlVJTkUpIDxzdW5zaGFraW5nQGdtYWlsLmNvbT6I
YAQTEQIAIAUCReOgiQIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJELyzxjoB
Ux6jz6YAn1tpRq4xZcin2cvCC6CdblXZ29O+AKDEdiBcSZx5udSw3/X1bq7NbRoU
WbkCDQRF46CVEAgAxohTys13AkSt000PCBy17NtV5vdCChmL/hrldfRZD9kurw4K
3ixfOcDZXQRmYE1BcQabJwntNPE090zLy84hM1wRc4aNW6iC2Ap1hHI4ZOntlbYd
ElShw7IDaM6+CXEiAnjG8kGldWD4zoCCBCSmSOHrSUDoBvY1UMPiZSUw+LYFh5Pw
hAyfr8gM8vc6oKy72/CSq1SdkA8YfylU90Wbka7xJbIKmTuFzqfJ5PGQ2c9yRecH
M/iuZ6lbSamPPecqw204HMYkTr6eRAGnQkCLx1fQeR3GlXi76OWg5WOFJ6dQAT0+
dsPsWY35F9vd/DbYlCJdbV4h2Fnl7l8MlMU7wwADBQf/Ym0821Qki6IfYMZ3cWR7
3gf6ClE7WstFBtSQF0fDAGqlBxzRGeuV4x75froPl09gxar8BUsYywh+1CuMr8Ur
VgAu3bWbWaJV+QJXF/EQfYHQdblcHEZ/aJbqeTX0Ot6cTeoDYRFRcVzibmipIEwk
c8hXedMungeiJUJuSHPRcT/Sa5v1xzk76KOdYVSZ1LZzSPps0QNRDE3C4+tG1oMO
3qhbc6n3Eekq5D2OjATmXnG4xBtwYq2KqRgaKbzOrUlDI0HImk3EHFxeDkZCE8VQ
dqF4iJcUXsdAiv3J26K0JSVPh0JPKTGHk9j/GIH2v2KXEmGliXlYGPZdEQXQVk1I
vohJBBgRAgAJBQJF46CVAhsMAAoJELyzxjoBUx6je7YAn2hZnY0TvjjxKfVAwxPR
zfvL4nSWAKCurJt4XCZkztb9orVulOPkv7kzXQ==
=+pwC
-----END PGP PUBLIC KEY BLOCK-----



Hey! I'm back again

I'm happy to be back again. I am working for MS now and looking backward to the time one year ago, I was looking for a job painfully.

I almost abandoned this Google blog last year. Because I found that it can't be accessed in China. It seems that something on Google blogger offended CCP or GFW.

Is it alright now?