Friday, October 17, 2008

Asynchronious call Delegate

参见 http://blogs.yodersolutions.com/net/?p=17
当我们声明了Delegate 变量(比如FooDelegate), 我们可以使用下面两种方法,来触发这个Delegate,
1. FooDelegate.Invoke(), 或者直接是FooDelegate().
2. FooDelegate.BeginInvoke().
第一种调用方法是同步执行这个delegate.
而第二种方式是异步执行这个delegate, 这个delegate会在另一个线程中被执行, 当前线程将继续执行其它代码. 需要说明的是, 一定要确保这个delegate有机会调用了EndInvoke(), 否则会有资源和内存泄漏.
异步调用的一个简单示例是:
subscriber.BeginInvoke(this, new EventArgs(), Callback, subscriber);
其中第3个参数是回调函数, 它的签名是AsynCallback delegate.当subscriber被执行完后, Callback会被调用.
其中第4个参数一定要是subscriber本身,或者是subscriber本身的一个wrapper, 这样在callback函数体中, 就能访问到subscriber本身来EndInvoke.


class Program
{
static void Main(string[] args)
{
EventPublisher eventPublisher = new EventPublisher();
eventPublisher.FooEvent += new EventHandler(eventPublisher_FooEvent1);
eventPublisher.FooEvent += new EventHandler(eventPublisher_FooEvent2);
Stopwatch sw = new Stopwatch();
sw.Start();
eventPublisher.AsynFireFooEvent();
Console.WriteLine(sw.ElapsedMilliseconds.ToString());
Console.ReadKey();
}

static void eventPublisher_FooEvent1(object sender, EventArgs e)
{
Thread.Sleep(100);
}
static void eventPublisher_FooEvent2(object sender, EventArgs e)
{
Thread.Sleep(200);
}
}



class EventPublisher
{
public event EventHandler FooEvent;


/// <summary>
/// synchronized invoke the all the subscriber services.
/// The process will return until all delegates executions are over.
/// </summary>
public void FireFooEvent()
{
if (FooEvent != null)
FooEvent(this, new EventArgs());
}


/// <summary>
/// Raising events asynchronously lets the event subscribers do their processing in another thread
/// and allows the thread that raised the event to continue processing.
/// </summary>
public void AsynFireFooEvent()
{
if (FooEvent != null)
{
Delegate[] subscribers = FooEvent.GetInvocationList();
foreach (EventHandler subscriber in subscribers)
{
//the 3rd arg should be a AsyncCallback type delegate. When one subscriber service will completed, the AsyncCallback method will be raised.

//the 4th arg should be the exact subscriber or subscriber wrapper, so that in callback function,
// we can access the subcriber to call EndInvoke()
subscriber.BeginInvoke(this, new EventArgs(), Callback, subscriber);
}
}
}



void Callback(IAsyncResult ar)
{
EventHandler raisedEvent = (EventHandler)ar.AsyncState;
raisedEvent.EndInvoke(ar);
}
}

Thursday, October 9, 2008

Auto build script by using NAnt

当我们想专门为一个C#项目架一个build服务器, 怎样对VS的solution文件进行自动build是个问题, 最简单的办法是, 编写一个bat文件, 运行这个bat文件就能完成solution的编译甚至运行单元测试 . NAnt是个很好的选择.
简单地讲, NAnt就是一个能将xml文件作为参数的命令行工具, 你可以在这个xml中按照NAnt的schema编写你想要完成的任务, 比如压缩文件, 比如删除目录, 当然NAnt最主要的作用是可以编写任务来批量编译c#的代码. 但是考虑到MS官方已经发布了msbuild, 所以一个比较好的做法是:在NAnt的参数xml文件中, 使用msbuild来编译.Net solution, 用NAnt本身的功能完成其它事情.
当然对于Nant新手来说, 编写这样的xml并不简单. 幸好我们有一些现成工具可以使用, Tree Surgeon就是这样一个开源项目(http://www.codeplex.com/treesurgeon).
TreeSurgeon不仅仅帮你编写xml的build文件(你可以根据需要做比较的修改), 而且会自动将NAnt以及NUnit等整理到一个tools目录. Treesurgeon创建的solution目录结构也很有参考价值, 比如和src目录同级别的目录, 还有bin, tools, lib, dist目录, 其中bin是存放solution的输出文件(需要在Visual studio中将这个solution的所有project的output目录制定到这个bin目录), dist目录是专门作为solution的release目录.
如果想测试这个xml文件中某些变量设置是否正确, 可以使用在project tag之下, 创建echo tag, 在用NAnt执行这个xml文件时, echo中的message信息就会被输出到屏幕上.


<echo message="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe"/>

下面的bat和xml是使用NAnt的示例(它是由TreeSurgeon生成, 并参考了Ninject源码中的NAnt build_xml文件). 使用非常方便,
cmd> build 或
cmd> build test
build.bat的内容是:


@tools\nant\NAnt.exe -buildfile:DbGear.build %*
rem pause

DbGear.build是一个xml文件, 用NAnt命令行程序执行这个xml文件(NAnt0.86版本如用msbuild tag来调用.net 3.5 的msbuild时候, 会有bug. 所以这个例子并没有使用常规的msbuild tag,而是使用 exec tag直接调用3.5版本的msbuild命令行程序)


<?xml version="1.0" ?>
<!--refer to treesurgeon on codeplex, and build file of ninject project-->
<project name="DbGear" default="compile" xmlns="http://nant.sf.net/schemas/nant.xsd">
<!--tell NAnt try to use msbuild from .net sdk 3.5-->
<property name="nant.settings.currentframework" value="net-3.5" />

<!--declare path variables-->
<property name="path.base" value="${project::get-base-directory()}"/>
<property name="path.src" value="${path.base}/src"/>
<property name="path.tools" value="${path.base}/tools"/>
<property name="path.build" value="${path.base}/bin"/>
<property name="path.dist" value="${path.base}/dist"/>
<property name="path.build.testresult" value="${path.build}/test-result"/>
<property name="path.build.debug" value="${path.build}/debug"/>

<!--define solution variables-->
<property name="version" value="1.0.0.0" overwrite="false"/>
<property name="file.solution" value="${path.src}/DbGear.sln"/>
<!--choose one suitable compiling config, eg Debug, Release or other customized config schema-->
<property name="build.configuration" value="debug"/>

<!-- define build targets -->
<target name="clean" description="Delete Automated Build artifacts">
<delete dir="${path.build}" if="${directory::exists(path.build)}"/>
<delete dir="${path.dist}" if="${directory::exists(path.dist)}"/>
</target>

<target name="init" description="create assembly out path">
<mkdir dir="${path.dist}" if="${not(directory::exists(path.dist))}"/>
<mkdir dir="${path.build}" if="${not(directory::exists(path.build))}"/>
<mkdir dir="${path.build.testresult}" if="${not(directory::exists(path.build.testresult))}"/>
<mkdir dir="${path.build.debug}" if="${not(directory::exists(path.build.debug))}"/>
</target>



<echo message="Build configuration:" />
<echo message="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe"/>

<!--nant0.86 has a bug with Framework 3.5 section, so we will use exec to call msbuild.exe of 3.5, rather than the regular msbuild tag -->
<!--
<target name="compile" depends="init" description="Compiles using the AutomatedDebug Configuration">
<msbuild project="${file.solution}">
<property name="Configuration" value="${build.configuration}" />
</msbuild>
</target>
-->

<target name="compile" depends="init" description="Compiles using the Debug Configuration">
<exec program="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe" >
<arg value="${file.solution}" />
<arg value="/verbosity:minimal" />
<arg value="/property:Configuration=${build.configuration}" />
<arg value="/property:WarningLevel=0" />
</exec>
</target>

<target name="test" depends="compile, unittest"
description="Compile and Run Tests" />

<target name="full" depends="clean, test"
description="Compiles, tests, and produces distributions" />

<!-- Internal targets -->
<target name="unittest" description="runs the unit tests">
<!-- Test Assembly -->
<exec program="${path.tools}/nunit/nunit-console.exe">
<arg value="${path.build.debug}/UnitTests.dll" />
<arg value="/xml=${path.build.testresult}/UnitTests-Results.xml" />
</exec>
</target>


<target name="dist">
<zip zipfile="${path.dist}\DbGear-${version}.zip">
<fileset basedir="${path.build.debug}">
<include name="**/*" />
<exclude name="**/*.pdb" />
</fileset>
</zip>
</target>


<target name="package-source">
<zip zipfile="${path.dist}\DbGear-${version}-source.zip" ziplevel="9">
<fileset basedir="${path.src}">
<include name="**/*.cs"/>
<include name="**/*.csproj"/>
<include name="**/*.sln"/>
<include name="**/*.txt"/>
<include name="**/*.build"/>
<exclude name="**/*.pdb" />
</fileset>
</zip>
</target>
</project>