June 2007 - Posts

Developers, Are You Underpaid Or Overpaid?

Phil Haack寫了一篇探討開發人員產能差異的好文章。可能是因為自己瘋狂熱愛Coding,一直以來對許多客戶、專案經理、公司主管貶低開發人員價值的錯誤認知頗不以為然,於是,這類的文章讀來格外心有戚戚焉。

對開發人員的種種誤解中,我最痛恨的是以下幾種:
1) 為什麼寫一個線上購物網站要花五十萬? 我小姨子的同事唸高中的兒子說給他五萬元就可以搞一個!
2) 每年多少資管畢業生呀? 程式開發人員去街上抓就一大把,還怕找不到人?
3) 不過就寫程式唄! 找誰來寫還不都一樣? 你專案不是缺人,為什麼不叫隔壁組現在沒事的老王加入你的Team? (Darkthread定律: 連砸了三個案子的人,在撐著不離職前都會很涼)
4) 當黑手寫程式有什麼前途? 把系統分析做好,外包給老印、老中的Programmer才是王道! 別把時間花在這種低層次的工作上,你要學習如何成長....

取得Phil的同意,我翻譯了原文並加上自己的詮釋,提供大家參考。


10 Developers For The Price Of One by Phil Haack

在人月神話(The Mythical Man-Month)一書中,Fred Brooks點出了程式師好壞所造成的產能差異,足以讓許多人跌破眼鏡。

Programming managers have long recognized wide productivity variations between good programmers and poor ones. But the actual measured magnitudes have astounded all of us. In one of their studies, Sackman, Erickson, and Grant were measuring performance of a group of experienced programmers. Within just this group the ratios between the best and worst performances averaged about 10:1 on productivity measurements and an amazing 5:1 on program speed and space measurements!
依據Sackman, Erickson, Grant對一群資深程式師進行的績效評量研究,最好與最差程式人員的產能差了10倍,在程式速度與空間上則相差有5倍之多。

Robert Glass在Facts and Fallacies of Software Engineering一書中,引用研究報告,提出更驚人的工作績效差異。

The best programmers are up to 28 times better than the worst programmers, according to “individual differences” research. Given that their pay is never commensurate, they are the biggest bargains in the software field.
依據個別差異研究,最好的程式師會比最爛的好上28倍,所以薪水篤定是被低估的,算得上是軟體業裡超划算的便宜貨。

換句話說,優秀的開發人員通常薪水是偏低的,而鳥鳥的開發人員則多領了不該領的錢。(譯註: 手爛掉 手爛掉 手爛掉...)

等一下,先別急著去拿離職單! 產能與薪水本來就不是呈現1:1的比例,薪水是依個人為公司帶來的價值及貢獻而定,而產能只是所謂"價值"中的一部分而已(雖然它佔了最大的比重)。話雖如此,我們還是衷心期盼產能上的明顯差異可以回饋到薪水上,但總是事與願違,為什麼?

因為經理人始終無法接受生產力好壞差異這麼大的事實,無論它已被許多研究重覆驗證過。他們的信仰堅定,可不會這麼容易被小小的事實所動搖,更何況放棄信仰豈不稱了無神論者的意? (哈!)

好,不開玩笑了。為什產能差異的事實如此難被接受? 謮容我代公司主管發言:

一個開發人員再怎麼厲害,寫程式的速度也不可能比另一個開發人員快28倍吧?

這是在評量開發人員產能時常見的謬誤! 開發產能不該用程式行數來衡量。無法完成工作的程式寫再多,終究還是不具任何生產力的垃圾。要衡量開發人員產能高低,實際上有很多指標可用,它們都基於一個主要原則(在此借用財務學的名詞)---TCO。

TCO - Total Cost Of Ownership,因持有而付出的總成本

一般來說,我一向只僱用我能找到最好的開發人員,但也不是沒有看走眼過。是的,連我也會出鎚。

想起在前公司有個開發人員,我將一個案子交給這位前同事接手,幾天過去了,沒有聽到任何消息,心想: 很好,事情應該很順利!

又幾天過去了,我晃到他座位旁去關心一下進度,他老兄居然跟我說,他搞不太懂其中幾項需求,原來這陣子的時間全都在"搞懂需求"這件事上空轉。

好的開發人員可以獨當一面,不用你掛心

這點出了好的開發人員比一般開發人員生產力高的主要原因之一---他們可以獨立掌握一個專案的進行。遇到需求不明的狀況,好的開發者會主動找到有權力決定需求的User,設法由他身上擠出答案來,絕不會因為需求不明而白白浪費一個星期的時間。

相同地,好的開發人員也不需要你時時盯著他的進度,反正遇到因難時,他會主動向你或其他同事尋求解決方案。

如果開發人員只是程式寫得快,卻沒有能力主動掌握專案的進行,生產力勢必高不到哪裡去。因為他會佔用你的時間,這部分也得算在他的成本上,要是你月領數十萬,成本就可觀囉。

好的開發人員程式Bug少

我曾經跟經一個老板大力稱讚的"程式快手"一起工作。呼! 他寫程式的速度可真快,製造Bug的速度也很嚇人 orz... 程式碼則亂得一塌糊塗,難以閱讀。

在老板對他的生產力衡量裡,QA部門重製錯誤(Reproduce Bug)及其他程式人員幫他修Bug所耗費的時間顯然被忽略了。不然這傢伙應該叫程式殺手,而非程式快手。

許多人只關心"完成"程式的時間,但這並不是程式全部的成本! 程式開發工作不該在開發者說"寫好了!"那一刻就停止計時,而應該要等到QA部門同意放行後,你才可以按下碼表上的停止鈕。

如我所說,生產力講求的不只是速度,而是效率,意指"迅速地達成目標"才算數。速度再快,方向錯了,充其實也只是在跑心酸的罷了。(譯註: 中國人有句成語一針見血---南轅北轍!)

好的開發人員會寫容易維護的Code

跟減少Bug一樣重要的是Code要力求易讀易懂、利於維護。當Code在螢幕上出現的那一刻起,就算是在維護Code了。

當開發人員要新增或修改系統功能時,艱澀難讀且難以修改的Code會浪費可觀的時間。好的開發人員藉由寫出易於維護的Code,讓隨後接手的其他團隊成員可以少幾根白頭髮,系統變更也可以更快被完成。換句話說,其他開發人員的產能在無形中也提升了!!

好的開發人員Do More With Less Code

好的開發人員另一項難能可貴之處是他總知道"何時不必寫Code"。我的一個朋友說得好:

"寫不如買,買不如借,借不如偷"

除了少數的例外狀況,"非我發明"症候群(NIH,Not Invented Here)是很可怕的生產力殺手。我曾看過開發者開始著手寫一套自己的Form Validation Framework(譯註: 靠! 我也寫過,但是在ASP時代,所以毋需自責,哈),直到我提醒他ASP.NET本身就有內建的Validation後才做罷。(雖然ASP.NET內建的功能並不怎麼樣,但至少會比那位盤古先生寫的來得好)

所有重新發明輪子的舉動都是徒勞無功,因為早已有人幫你把程式寫好了。在大多數的情況下,目標專一的人通常會有較好的表現;遇到類似的情況,借重現有的Library,對生產力的提升效果應該會讓你大吃一驚。

不過,要提醒大家小心一些延展性及彈性欠佳的3rd Party Library,尤其是針對一些很專門的用途,選錯Libary,可能得耗費難以想像的時間在客製化上,猶如橫柴入灶,得不償失。

就算找不到合適的Library得DIY,好的開發人員也能用較少的Code(但是無損於可讀性)完成更多的功能。例如,要由一個複雜的字串擷取出特定的部分,菜鳥會傻傻地寫個數百行程式去彈性判斷各種字元排列組合,而識途老馬則會用Regular Expression兩三行搞定。(對,有人會說RegEx的語法跟天書差不多,但我想學會它總還是比在數百行的SubString、IndexOf中找頭緒來得容易多;更何況把它學好,可以應用的地方的多得很,絕對超值!)
譯註: 菜鳥程式師們想要"轉大人"嗎? 想學.NET Regular Expression可參考這篇文章

回到TCO

以上所說的這些特性,都讓好的開發人員具有較低的TCO。不要被Ownership這個字所迷惑,這裡強調的是成本(Cost),對公司來說就是付出的薪水。

好的開發人員可以用較少的Code完成更多的功能,寫出的程式不但Bug少也容易閱讀及維護,大幅減輕了QA部門、其他同事及管理者的工作負擔,無形中增加了每個人的生產力,這是為什麼產量會差上28倍最重要的原因。

希望這番剖析可以說服管理者,好的開發人員真的具有如研究報告中所說的超高生產力,另外一方面,跟老板要求加薪28倍的任務,就留給各位讀者當作課後練習。

Posted 30 June 2007 09:11 AM by Jeffrey | no comments
Filed under:
Introduction To .NET Regular Expression

試想以下的情境:

  • 公司最近要研發一套類似Google的網頁搜索引擎,你負責開發網頁擷取器核心,其中最棘手的部分是要具備由一個網頁再延伸至其所超連結出去的其他網頁…
  • 行銷人員拿來一份十萬筆客戶地址檔,請你解析出縣市、郵遞區號、地址三個欄位轉入CRM系統,但要命的是原始資料雜亂不已,有的郵遞區號在前,有的在中間…
  • 你加入了檢索引擎過濾器(Filter)的開發團隊,目標是將各式資料檔中的純文字部份提取出來,你的第一件任務是簡單的HTML檔案解析,但是,HTML原始碼千變萬化…

以上的挑戰當然不是每位程式開發者都有機會遭遇,但應該沒有人會否認能寫出這等程式的傢伙還真有兩把刷子。

好像還是有些人不太曉得Regular Expression的威力,簡單來說,針對複雜的文字串處理(例如: 將一篇文章中所有出現的URL都挑出來),Regular Express vs 土法鍊鋼,會是 兩行 vs 200行 的強烈對比。

以下這篇文章是我小時候投稿RUN!PC雜誌的作品(即使現在寫RegEx,忘了語法時我也是先查這篇,足見其實用性),介紹.NET中一群好用但常被遺忘的類別--Regular Expression,並展示如何讓文字處理程式的發展工作單純一點、輕鬆一些。Regular Expression的技巧學好了,應用無窮(連UltraEditor、Grep等Command Utility都看到到它的蹤跡),保證值回票值,Check It Out!

文章下載

Posted 30 June 2007 08:40 AM by Jeffrey | 11 comment(s)
Filed under: ,
TIPS-Conversion Between enum, int And string

.NET中的Enumerate可以提高程式的嚴謹性及可讀性,我最愛的一個例子是DateTime的DayOfWork Enumeration。

if (DateTime.Now.DayOfWeek==DayOfWeek.Friday)是不是比DateTime.Now.DayOfWeek==5更容易閱讀呢? 而當要求使用者傳入星期幾作為參數時,也不必擔心User傳入-1 或18來搗蛋。

不過,在某些時候,我們還是需要將星期三轉成數字3,或是將"Sunday"解讀成為DayOfWeek.Sunday。廢話不多說,請大家看以下的程式,它完整地示範了如何進行int<->enum, string<->enum間的轉換:

int i = 3;
//Convert From Integer to Enum
DayOfWeek dw = (DayOfWeek)Enum.ToObject(typeof(DayOfWeek), i);
Console.WriteLine(string.Format("int [{0}] -> enum [{1}]", i, dw));
//Convert From Enum To Integer
dw = DayOfWeek.Thursday;
i = (int)dw;
Console.WriteLine(string.Format("enum [{1}] -> int [{0}]", i, dw));
//Convert From Enum To String
dw = DayOfWeek.Friday;
string s = dw.ToString();
Console.WriteLine(string.Format("enum [{1}] -> string '{0}'", s, dw));
//Convert From String To Enum
s = "Sunday";
dw = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), s);
Console.WriteLine(string.Format("string '{0}' -> enum [{1}]", s, dw));

執行結果如下:

int [3] -> enum [Wednesday]
enum [Thursday] -> int [4]
enum [Friday] -> string 'Friday'
string 'Sunday' -> enum [Sunday]

Posted 28 June 2007 02:48 PM by Jeffrey | no comments
Filed under: ,
KB-Connection Closed Exception of FtpWebRequest

幾天前我寫了一篇Post介紹如何用System.Net.FtpWebRequest開發一個支援續傳功能的FTP Client。

在專案中開始使用它來傳大檔時,卻發現不知FtpWebRequest是不是為了炫耀它的續傳功能,在花了半小時傳完一個400MB的ZIP檔之後,都會觸發一個"The underlying connection was closed: An unexpected error occurred on a receive."的Exception,但是檔案的大小正確,代表每一個Byte都順利傳回來了,但就是逼得我得Run第二次"續傳餘下的0 Byte",程式才會正常結束。

同樣的程式若下載的是5MB的ZIP檔,則不會發生問題。因為"Size Matters",所以我推斷可能可能與Timeout之類的屬性有關。雖然發現FtpWebRequest.Timeout官方文件寫錯了,預設值是100,000ms而不是無限大,但是測試發現它並不是導致問題的原因。

後來想到另一件事,傳統的FTP軟體的訊息視窗中總會看到定期送出NOOP指令來保持連線不被Server切斷(依據這篇文件,一般的FTP Server,只要5分鐘沒收到Control Connection傳來的任何訊息,就會中斷連線),而FtpWebRequest並沒有實做這類的機制。因此,只要下載時間超過FTP Server的Idle Timeout,在Stream.Close()會觸發檢查Connection的Logic,雖然資料已順利傳完,但由於Control Connection已經Idle過久被切了,於是拋出以下錯誤:

Error: The underlying connection was closed: An unexpected error occurred on a receive.
   at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
   at System.Net.FtpWebRequest.RequestCallback(Object obj)
   at System.Net.CommandStream.Abort(Exception e)
   at System.Net.CommandStream.CheckContinuePipeline()
   at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
   at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
   at System.Net.FtpDataStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()

事到如今,要解決這個問題有幾種手段:

  1. 去找有實作NOOP Keep Alive機制的3rd Party .NET Component,如: Rebex FTP for .NET,但需考量採購成本
  2. 自己實作NOOP機制,但需要處理複雜的Control Connection與Data Connection問題,有陷入"重新發明輪子"迷思的疑慮,還不如去買現有元件
  3. 由於資料已順利傳完,實際上我們已不再需要Control Connection,而是Stream.Close()時所觸發的制式檢查,那麼忽略Connection已斷的事實又何妨?
    雖說將Exception納為正常流程有違反一般的效能準則,但進入Exception流程所多出的時間應該不及1ms,相較於超過5分鐘的下載時間,倒是可以被忽略。

偷懶的我決定用方法3),將先前Post程式的片段修改如下:

//2007-06-27 因為沒有NOOP的Keep Alive機制
//當下載時間超過FTP Server的Idel限制,會在Stream.Close()時
//發生以下錯誤:
//The underlying connection was closed: 
//An unexpected error occurred on a receive.
//若檔案已順利下載完成,則忽略此一WebException
bool bFinish = false;
 
try
{
    using (Stream stm = ftpResp.GetResponseStream())
    {
        //由於檔案頗大,因此分Block寫入
        byte[] buff = new byte[2048];
        int len = 0;
        //取得要下載的檔案大小
        long totalSize = ftpResp.ContentLength;
        while (fs.Length < totalSize)
        {
            len = stm.Read(buff, 0, buff.Length);
            fs.Write(buff, 0, len);
        }
        fs.Flush();
        //檔案完整傳完, bFinish=true
        bFinish = (fs.Length == totalSize);
        fs.Close();
        //下載時間過久時,stm.Close()會觸發Exception
        stm.Close();
    }
}
catch (System.Net.WebException we)
{
    //若未傳完才要觸發Exception
    if (!bFinish) throw we;
}
ftpResp.Close();
KB-FtpWebRequest.Timeout Default Value

用FtpWebRequest下載大檔案時,出現了Connection Closed Exception,除錯過程中懷疑是逾時間題,所以我找到FtpWebRequest有以下兩個Timeout相關屬性:

  • Timeout: 指完成Request的逾時限制,文件上說預設值是Infinite(-1),永不逾時。
  • ReadWriteTimeOut: 每次寫入或讀取Stream動作所允許的Timeout。

文件說Timeout的預設值是無限大,但為了謹慎起見,我還是進入Debug Mode,看了FtpWebRequest Instance的Timeout值,100000!! What? 不是應該是-1嗎?

這激發了我的好奇心,套足柯南的名言: "真相只有一個!",再挖進去,用Reflector解析了System.Net.FtpWebRequest,看到以下的Code:

static FtpWebRequest()
{
    m_AsyncCallback = 
    new GeneralAsyncDelegate(FtpWebRequest.AsyncCallbackWrapper);
    m_CreateConnectionCallback = 
    new CreateConnectionDelegate(FtpWebRequest.CreateFtpConnection);
    DefaultFtpNetworkCredential = 
    new NetworkCredential("anonymous", "anonymous@", string.Empty, false);
    s_DefaultTimeout = 0x186a0;
    s_DefaultTimerQueue = 
    TimerThread.GetOrCreateQueue(s_DefaultTimeout);
}

Bingo! 0x186a0==100,000,就是Debug時看到的數字。

還在想如何通報此一文件錯誤,就驚喜地發現新版的MSDN官方文件提供了Developer Community直接在文件上寫註解(Community Content)的功能。於是,我在MSDN Library留下了我第一個腳印

Web 2.0時代,Content Provider與Consumer的界線愈來愈模糊,另一場資訊革命正悄悄展開!

TIPS-Remember User's Preference with Cookie

在Key單UI的實務上,有個有趣的現象。假設Key單Web介面中有個下拉選單(DropDown, <SELECT>),張三因為承做甲業務,所以永遠都選第三個選項,而李四較常在處理乙業務,所以總是選第五個選項。如果介面在顯示時可以預設就停在使用者最常用的選項上,每次Key單時就可以少敲幾次鍵。

此時有幾種設計方法: 第一種是建立User的Profile,由其負責的業務來判斷預設選項,再不然就是開放User設定自己偏好的預設選項。我個人更偏好一種更直覺更簡單的做法---用Cookie記住User上回選什麼就好了,不用在Server/DB上找地方存User設定,也不用擔心User可能一陣子做甲業務,一陣子搞乙業務,設定得調來調去。總之,上次輸入什麼,這次就預先帶出什麼值,KISS(Keep It Simple and Stupid)永遠是最美妙滴!!

底下是一段用ASP寫的Sample Code(ASP.NET裡的寫法大同小異),Product Category下拉選單會用Cookie記住你上次選的值,下次再進入頁面時會預設停在上面。

有幾點補充一下:

1) 記得要設Cookie的Expires屬性,否則Cookie會在Browser關掉後立刻消失。

2) 設好Cookie後,每次發Request到Web時都會夾帶你的Cookie內容(是的,每個Request都要!),所以別存太多資料在Cookie中(記住! 它只是Cookie,別把它當Pizza),以免形成傳輸負擔。

3) oSel.options[i].value=='<%=sDefaultProdCatg%>'的寫法記得要Escape掉單引號,至於換行符號,應該是可以偷懶不處理囉。

<% Option Explicit %>
<%
Dim sDefaultProdCatg
Const cookieName="AfaProdKeyIn"
If (Request("txtClientName")<>"") Then 'Postback
 'Set Expires to keep this cookie for one month, 
 'If Expires is not set, cookie will be gone after browser closed
 Response.Cookies(cookieName).Expires = DateAdd("m", 1, Now)
 Response.Cookies(cookieName)=Request("selCategory")
 Response.Write "<script>alert('Inserted!');</script>"
 Response.End
Else
 sDefaultProdCatg = Replace(Request.Cookies(cookieName), "'", "\'")
End If
%>
<html><body>
<form method="POST" action="userPref.asp">
Client Name: <input type="text" name="txtClientName" ><br>
Product Category: 
 <select name="selCategory">
 <option value="Notebook">Notebook</option>
 <option value="PC">PC</option>
 <option value="LCD">LCD</option>
 </select><br>
<script type="text/javascript">
var oSel = document.getElementById("selCategory");
for (var i=0; i<oSel.options.length; i++)
 if (oSel.options[i].value=='<%=sDefaultProdCatg%>') {
 oSel.selectedIndex=i;
 break;
 }
</script>
<input type="submit" value="Add">
</form>
</body></html>
TIPS-Design A Mouseless Web UI

經歷過DOS->Windows轉換的老人家們應該不會忘記第一次踏著滑鼠在視窗上衝浪的莫名感動,一樣是打電腦,用滑滑點點取代一長串按鍵,感覺就是不一樣,一瞬間,人生彷彿由黑白變彩色~~~

隨著GUI視窗化介面的普及,大部分的開發者開始認定充分使用滑鼠才是最人性化的界面設計方式,但是世界上有一群人持著不一樣的看法。

滑鼠操作的動作範圍較小,2D空間定位的直覺性是鍵盤無從取代的,但這仍無法改變我們必須依賴鍵盤輸入文字的事實。當你被要求每天輸入幾百上千筆相似的資料,若輸入幾個字元就要滑一下滑鼠,意味著每幾秒鐘需轉動手肘將手掌從鍵盤移駕到滑鼠背上,接著得眼睛盯著螢幕做手眼協調練習,轉轉手腕,動動手指後再回到鍵盤上。一天下來,等同於手臂做了上萬次伸展運動,除了累積職業傷病給付的領取資格外,依據工業工程(很巧地,工工也叫IE,沒想到二技唸工管的我,在這個議題上,居然有在電腦科班大軍面前"裝懂"的一天,哈!)裡的動作經濟原則(Motion Economy Principle By Ralph M. Barnes)三級動作(指牽動手肘、手腕、手指的動作)絕對比一、二級動作不利於工作效率。

換句話說,對於重度的Key In使用者來說,Keyboard動作肯定無法避免(總不能用螢幕小鍵盤去點出所有的字母吧?),所以設法讓所有的操作以Keyboard完成,減少使用者切換滑鼠/鍵盤的次數,對於降低使用者的肉體疲勞與精神折磨(可以閉著眼睛敲Key解決,絕對比盯著螢幕滑滑鼠來得輕鬆)及提高使用者操作流暢度,都有神奇的改善效果。

除了全程使用Keyboard外,還有一個設計重點: 務必減少使用者必須按鍵的次數,可以敲兩個鍵做到的事,就不要逼使用按三下才完成。這些操作介面上的改善,其實都適用工作研究中的諸項原理,誰說拿著碼錶計時法只能用來提升工廠作業員的工作流暢度?

以下提供幾則設計Mouseless Web UI的小技巧:

  1. 善用tabindex屬性
    幾乎各Input都有這個屬性,使用者在輸入完一個欄位後,按下Tab鍵,就將焦點移到下一個要輸入的欄位上,讓使用者不要花時間自行切換焦點,可以減少可觀的按鍵次數。
  2. 焦點快速鍵
    除了tabindex,若使用者想隨機跳到某個欄位輸入資料,<input>還有個accesskey屬性,<input type="text" accesskey="f">代表使用者在IE按下Alt-F,在FireFox按下Alt-Shift-F,焦點就會立刻跳到該欄位上。
    Radio Button/Checkbox也支援accesskey,按下後可直接checked!
  3. Radio Button & Dropdown快速鍵
    當焦點移在Radio Button(<input type="radio">)或Dropdown(<select>)時,Browser都支援按上、下、左、右鍵切換選項。Dropdown時,按下字母鍵還可快速跳到該字母開頭的Option上。
  4. 按Enter鍵送出表單
    ASP.NET 2.0新增了DefaultButton,可以讓使用者按Enter時自動Submit表單,詳情請見先前的KB
  5. 以AutoComplete取代冗長的Dropdown List
    當選項很多時,使用者必須使用下捲在Dropdown的選項大海中查找,我個人的建議是利用AutoComplete的AJAX技巧取代Dropdown。
囧字恆久遠,一"刻"永流傳

"囧"(音炯,說文解字在此)這個字在中國字典裡沈睡了幾千年,最近這幾年才托網路火星文的福,火熱了起來,連七年級、八年級生都能朗朗上"手"。我打睹全宇宙裡99.999...%的時間這個字都是以電子資料形式存在,只有千萬分之一的比率是出現在古文史籍的紙本中,那麼被刻在石碑上的機率有多高? 應該低於十億分之一吧!?

我遇到了!!

在古碑上跟囧字相見歡,真是特殊的經驗...

Posted 25 June 2007 03:56 AM by Jeffrey | 1 comment(s)
Filed under:
【茶包射手專欄】Why Bind("Blah") Doesn't Work?

寫TemplatedControl時,ASP.NET 2.0有個好用的新函數Eval("FieldName"),可以在DataGrid、GridView等物件的Template Column中,將DataItem中某個欄位的值指定給Label、TextBox之類的;在ASP.NET 1.1時代,這得寫成DataBinder.Eval(Container, "DataItem.FieldName"),相較之下,新寫法簡潔多了! 我查到大陸朋友寫的一篇精彩文章(同一篇文章被轉貼得到處都是[Goggle共找到1350處 orz],但幾乎都沒註明原出處,只好隨便貼一個URL給大家參考,並向不知名的原作者致上敬意),用Reflection的技巧去追出其實Eval("FieldName")內部呼叫了DataBinder.Eval(this.Page.GetDataItem(),  "FieldName"),文中甚至還探討了不同做法的效能,這下Eval函數的原理就很清楚了。

Eval有個好兄弟叫Bind,用在雙向的Binding上,例如EditItemTemplate中,需將User在TextBox輸入的內容回寫到DataItem。

同事小娟問了一個有趣的問題,她發現在DataBinding中呼叫自訂函數時,<%# MyFunc(Eval("FieldName") %>的寫法可以過關,但<%# MyFunc(Bind("FieldName") %>卻會傳回
Compiler Error Message: CS0103: The name 'Bind' does not exist in the current context

我認為這是因為Data-Binding Syntax裡,並不是直接將整段內容當成Source Code拿來Compile,而是進行了一些Parsing及轉換。但推論歸推論,要怎麼證明呢?

   1:      <form id="form1" runat="server">
   2:      <div>
   3:      <asp:GridView ID="GridView1" runat="server" 
   4:          AutoGenerateColumns="False">
   5:          <Columns>
   6:              <asp:TemplateField HeaderText="NameEval">
   7:                  <ItemTemplate>
   8:                      <asp:Label ID="Label1" runat="server" 
   9:                      Text='<%# Eval("Name_Eval") %>'></asp:Label>
  10:                  </ItemTemplate>
  11:              </asp:TemplateField>
  12:              <asp:TemplateField HeaderText="NameBind">
  13:                  <ItemTemplate>
  14:                      <asp:Label ID="Label2" runat="server" 
  15:                      Text='<%# Bind("Name_Bind") %>'></asp:Label>
  16:                  </ItemTemplate>
  17:              </asp:TemplateField>
  18:              <asp:TemplateField HeaderText="NameFuncEval">
  19:                  <ItemTemplate>
  20:                      <asp:Label ID="Label3" runat="server" 
  21:       Text='<%# CvrtStr(Eval("Name_Func_Eval")) %>'></asp:Label>
  22:                  </ItemTemplate>
  23:              </asp:TemplateField>
  24:              <asp:TemplateField HeaderText="NameFuncBind">
  25:                  <ItemTemplate>
  26:                      <asp:Label ID="Label4" runat="server" 
            Text='<%# CvrtStr(Bind("Name_Func_Bind")) %>'></asp:Label>
  28:                  </ItemTemplate>
  29:              </asp:TemplateField>            
  30:              
  31:          </Columns>
  32:      </asp:GridView>
  33:      </div>
  34:  
  35:      </form>  

以上的程式碼是跑不動的,你會得到前述找不到Bind的錯誤,但錯誤頁面本身其實已提供了線索,只是常被忽略而已。我們都知道ASPX的Code最後也會被Parsing成.cs/.vb即時Compile再與Code-Beside/Code-Behind的DLL一起整合執行,當ASPX端程式編譯失敗時,下方會出現:

Show Detail Compiler Output:

Show Complete Compilation Source:

按下Show Complete Compilation Source,就可以看到ASPX怎麼變成一個複雜的.cs。Trace其中,可以找到前面程式中四個Binding分別被轉成:

1.Eval("Name_Eval")

dataBindingExpressionBuilderTarget.Text = System.Convert.ToString( Eval("Name_Eval") , System.Globalization.CultureInfo.CurrentCulture);

2.Bind("Name_Bind")

dataBindingExpressionBuilderTarget.Text = System.Convert.ToString(this.Eval("Name_Bind"), System.Globalization.CultureInfo.CurrentCulture);

3.CvrtStr(Eval("Name_Func_Eval"))

dataBindingExpressionBuilderTarget.Text = System.Convert.ToString( CvrtStr(Eval("Name_Func_Eval")) , System.Globalization.CultureInfo.CurrentCulture);

4.CvrtStr(Bind("Name_Func_Bind")

dataBindingExpressionBuilderTarget.Text = System.Convert.ToString( CvrtStr(Bind("Name_Func_Bind")) , System.Globalization.CultureInfo.CurrentCulture);

答案揭曉了,原來根本不存在Bind這個Method,當我們寫Bind時,實際上它會被轉成this.Eval("FieldName"),而當它被包成自訂函數CvrtStr的參數時,則不會被解讀,而保持Bind(..)的寫法,導致Bind函數不存在的Build Error!

第一個迷團解開了,但如果Bind與Eval的差別只在於有沒有加this.並不合情理,如文件所說,Bind主要用來雙向的Data-Binding,所以應該會與Eval有明顯不同才對。仔細再挖下去,在無法Build的ASPX Source Code中,我們可以找到另一段Code:

  public System.Collections.Specialized.IOrderedDictionary 
    @__ExtractValues__control8(System.Web.UI.Control @__container) 
  {
      System.Collections.Specialized.OrderedDictionary @__table;
      System.Web.UI.WebControls.TextBox TextBox1;
      
      #line 13 "C:\WWW\TestWeb\BindTest.aspx"
      TextBox1 = ((System.Web.UI.WebControls.TextBox)
    (@__container.FindControl("TextBox1")));
      
      #line default
      #line hidden
      
      #line 13 "C:\WWW\TestWeb\BindTest.aspx"
      @__table = 
    new System.Collections.Specialized.OrderedDictionary();
      
      #line default
      #line hidden
      
      #line 13 "C:\WWW\TestWeb\BindTest.aspx"
      if ((TextBox1 != null)) {
          @__table["Name_Bind"] = TextBox1.Text;
      }
      
      #line default
      #line hidden
      return @__table;
  }

Bingo!! 這就是Bind與Eval的最大差別!!

當ASP.NET在Parsing Data-Binding Syntax時,遇到了Bind,除了加上設定TextBox.Text的程式碼,還會加上將TextBox.Text存入OrderedDictionary的邏輯,換句話說,之前的推論沒有錯: Bind算是Syntax,而非特定的Method。

真相大白,收工!

KB-IE Operation Aborted
   1:  <html><body>
   2:  <img src="banner">
   3:  <table>
   4:  <tr><td>First Line</td></tr>
   5:  <tr><td><input type="text" name="txt1">
   6:  <script type="text/javascript" src="inc.js"></script>
   7:  <script type="text/javascript">
   8:      //call some function in inc.js
   9:  </script>
  10:  </td></tr>
  11:  </table></body></html>

我寫了一個類似上面的網頁,Include一個js,然後呼叫js裡的特定函數處理TextBox,看似OK,但以IE開啟時,卻會彈出Operation Aborted的MessageBox。網頁只顯示了上方的Banner Image,感覺上還來不及顯示表格,IE就出問題中止了。

Google了一下,找到這篇說明,狀況跟我十分類似。照著說明,將<script>移到</table>下方,問題迎刃而解。

【教訓】

不要在<td></td>間夾入<script> Block,以免發生不測。
不要在<table>....</table>的勢力範圍內夾入<script> Block或其他奇奇怪怪的東西,以免發生不測
(Update @ 2007-06-25,謝謝Steve補充)

Updated 2008-01-23 by Jeffrey
正解出現,在<div></div> <table></table>中修改上層元素的innerHTML或appendChild就會出事,請見: http://blog.darkthread.net/blogs/darkthreadtw/archive/2008/01/23/tips-ie-operatino-aborted-again.aspx

TIPS-Implement a Resumable FTP Download with .NET 2.0

最近手上的案子需要以FTP方式取回數百MB到上GB的ZIP檔案,過去我的做法會一個登入及下載的Script,作為FTP command line utility的輸入來源,再以Shell方式啟動,如以下的例子:

   1:  //產生FTP Script檔案
   2:  string ftpScriptFile = Server.MapPath("TempFolder\\ftp.script");
   3:  Process p = new Process();
   4:  StreamWriter sw = 
   5:      new StreamWriter(ftpScriptFile);
   6:  sw.WriteLine("username");
   7:  sw.WriteLine("password");
   8:  sw.WriteLine("bin");
   9:  sw.WriteLine("cd /download");
  10:  sw.WriteLine("get some.zip");
  11:  sw.WriteLine("quit");
  12:  sw.Close();
  13:  //呼叫FTP command line utility
  14:  p.StartInfo = new ProcessStartInfo("cmd.exe", 
  15:  "/c ftp -s:\"" + ftpScriptFile + "\" ftp.darkthread.net");
  16:  //不顯示FTP Cmd視窗
  17:  p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
  18:  p.Start();
  19:  p.WaitForExit();
  20:  //狡兔死 走狗烹 Script檔刪除
  21:  File.Delete(ftpScriptFile);

 這個方法不算漂亮,但是簡單耐用,也不太會出錯。

之前瞄過.NET 2.0的新功能,知道它多了支援FTP的內建物件--System.Net.FtpWebRequest,更讚的是,它居然還可以支援斷線續傳的功能,對於這次可能破GB的大檔下載來說,真是一整個酷呀!

不囉嗦! 程式範例如下:

   1:  private void test()
   2:  {
   3:      //宣告FTP連線
   4:      FtpWebRequest ftpReq = (FtpWebRequest)
   5:          WebRequest.Create("ftp://ftp.darkthread.net/download/some.zip");
   6:      //下載檔案
   7:      ftpReq.Method = WebRequestMethods.Ftp.DownloadFile;
   8:      //指定Username/Password
   9:      ftpReq.Credentials = new NetworkCredential("username", "password");
  10:      //指定BIN模式
  11:      ftpReq.UseBinary = true;
  12:      string zipFile = "c:\\temp\some.zip";
  13:      //支援續傳功能
  14:      FileInfo fi = new FileInfo(zipFile);
  15:      FileStream fs = null;
  16:      //檢測檔案是否存在
  17:      if (fi.Exists)
  18:      {
  19:          //檔案若存在,由剛才的中斷點繼續
  20:          ftpReq.ContentOffset = fi.Length;
  21:          fs = new FileStream(zipFile, FileMode.Append, FileAccess.Write);
  22:      }
  23:      else //檔案不存在時重新建立新檔案
  24:          fs = new FileStream(zipFile, FileMode.Create, FileAccess.Write);
  25:      //建立FTP連線
  26:      FtpWebResponse ftpResp = (FtpWebResponse)ftpReq.GetResponse();
  27:      //取得下載用的Stream物件
  28:      using (Stream stm = ftpResp.GetResponseStream())
  29:      {
  30:          //由於檔案龐大,以Block方式多批寫入
  31:          byte[] buff = new byte[2048];
  32:          int len = 0;
  33:          while (fs.Length < ftpResp.ContentLength)
  34:          {
  35:              //取得資料長度
  36:              len = stm.Read(buff, 0, buff.Length);
  37:              fs.Write(buff, 0, len);
  38:          }
  39:          stm.Close();
  40:   
  41:      }
  42:      fs.Flush();
  43:      fs.Close();
  44:  }

Update @ 2007-06-27
以上的程式碼在下載大型檔案時會因超過FTP Server Control Connection Timeout而發生Connection Closed Exception,詳情請看這裡

KB-Transaction Error When Query Oracle Database Links

同事遇到一個狀況,使用OleDbCommand查詢ORACLE上的Database Link時,系統回報以下錯誤:
ORA-02041: client database did not begin a transaction

Goggle了一下,發現問問題的人挺多的,但回答很分歧,沒法一下子得到正解。反覆測試後,我得到的結論如後。

當使用Oracle Database Link時,因涉及兩台以上的Oracle Server,自動啟用Distributed Transaction可以被理解(SQL Server亦是如此)。但網路上的討論似乎都集中火力在避免啟用Distributed Transaction上,而不是研究怎麼順利啟用它。Anyway,我試出兩種成功的做法。第一種是用ORACLE提供的OLEDB,關鍵是要將Registry中的HKLM\SOFTWARE\ORACLE\OLEDB\DistribTX由1改為0,表示停用分散式交易(我懷疑這會導致所有的Oracle OLEDB操作都失去分散式交易的能力,值得留意)。程式碼如下:

   1:  static void testOraOle()
   2:  {
   3:      OleDbConnection cn =
   4:          new OleDbConnection(
   5:          "Provider=OraOLEDB.Oracle; Data Source=Ora1; " +
   6:          "User Id=blah; Password=blahblah;");
   7:      cn.Open();
   8:      OleDbCommand cmd = 
   9:          new OleDbCommand("select * from myTable@Ora2", cn);
  10:      OleDbDataReader dr = cmd.ExecuteReader();
  11:      while (dr.Read())
  12:          Console.WriteLine(dr[0].ToString());
  13:      dr.Close();
  14:      cn.Close();
  15:  }

使用ODP.NET時,關鍵則在Connection String中要加上Enlist=false停用分散式交易,如下:

   1:  static void testOraODP()
   2:  {
   3:      //程式碼要宣告using ODP = Oracle.DataAccess.Client;
   4:      ODP.OracleConnection cn = 
   5:          new ODP.OracleConnection(
   6:          "Data Source=Ora1; User Id=blah; Password=blahblah; " +
   7:          "Enlist=false;");
   8:      cn.Open();
   9:      ODP.OracleCommand cmd = 
  10:          new ODP.OracleCommand("select * from myTable@Ora2", cn);
  11:      ODP.OracleDataReader dr = cmd.ExecuteReader();
  12:      while (dr.Read())
  13:          Console.WriteLine(dr[0].ToString());
  14:      dr.Close();
  15:      cn.Close();
  16:  }

至於System.Data.OracleClient,在我的測試中,即使加上Enlist=false仍會傳回相同錯誤,是否註定無法支援或仍有其他的解法,暫時不得而知,若有人有成功案例再反應給我吧!

Posted 22 June 2007 02:12 PM by Jeffrey | 1 comment(s)
Filed under: ,
Microsoft SMTP Service Mini FAQ

Microsoft SMTP Service是從NT時代就有的實用服務,陽春歸陽春,拿來當作網站系統派送通知的服務綽綽有餘。這幾年下來,陸續有些處理SMTP Service的經驗,在此以FAQ的方式做個分享。

1.為什麼我的信寄不出去?
A: 寄信Client多半會要求輸入SMTP Server IP、Port Number等資料。有些人會抱怨明明就設好127.0.0.1的25 Port,使用Telent 127.0.0.1 25的方式也可以連線成功,但信就是寄不出去。
最常見的理由是SMTP Service的Mail Relay(郵件轉寄)功能被關閉了! 由於垃圾信發送者常常會在網路上尋找支援Relay功能的SMTP主機當作發送垃圾信的跳板。近年來,基於安全理由,各家SMTP Server的預設值都會停用Mail Relay功能,所以請記得到IIS管理員中將127.0.0.1加入允許Relay的IP清單。
有個非常實用的SMTP偵測技巧是利用Telnet檢視SMTP交談中的訊息,如下所示,就可以清楚看出問題出在Relay Denied。關於Telnet 25 Port的技巧可以參考MS的這篇KB,至於如何Config SMTP Service,則可以看另一篇KB

C:\>telnet 127.0.0.1 25

220 blah.com.tw Microsoft ESMTP MAIL Service, Version: 5.0.2195.6713 ready at
 Thu, 21 Jun 2007 22:14:42 +0800
helo darkthread
250 blah.com.tw Hello [10.220.1.91]
mail from: jeffrey@utopia.com
250 2.1.0 jeffrey@utopia.com....Sender OK
rcpt to: jeffrey@darkthread.net
550 5.7.1 Unable to relay for jeffrey@darkthread.net
quit
221 2.0.0 blah.com.tw Service closing transmission channel

Connection to host lost.

C:\>

 2.我沒開放Relay,為什麼某些程式的信還是寄得出去?
A.前面強調過SMTP Svc必須要開放Mail Relay限制才能寄Mail出去,但有人抗議,他的SMTP Svc Mail Relay一直以來都是關閉的,信件照樣寄得嗄嗄叫!
inetpub/wwwroot相信大家都耳熟能詳,但inetpub下還有個名不見經傳的mailroot目錄。mailroot下有幾個好玩的目錄: Badmail用來放SMTP Svc寄不出的退信、Drop是被丟棄的信、Mailbox則用來存放寄給本機使用者的信(MS SMTP Svc其實有收信功能的,只要Domain Name設對了,user@yourSmtpDomain.com的信就會被放到這裡),Queue、Route、SortTemp都是傳送過程中暫存信件用的。[2008-07-14更新: Will保哥提供了一些Windows 2003上觀察到的不同,請參見留言] 如果信件卡住沒送出去,則可以在Queue中看到大排長龍。最後的重頭戲來了,有個Pickup目錄,你只要把信組成.eml的格式寫進去,MS SMTP Svc就會幫你寄出去,效果與使用TCP 25連上127.0.0.1完全相同,但這種做法不受Relay的限制。(大概是SMTP將有權限在本機寫入檔案的Client視為合法使用者的緣故)
例如MS的CDONTS.DLL,不需要指定SMTP Svc IP & Port就可以寄信,關鍵也在於它是用Pickup目錄來交寄郵件。

3.要怎麼Trouble-Shooting SMTP Svc?
A.前面已經提示過一些技巧了,在此再做個整理:
1) 用Telnet 127.0.0.1 25的方法Debug
2) 檢查Badmail, Pickup, Queue等Mailroot下的目錄
3) SMTP Svc跟IIS一樣可以記Log,但預設是關閉的,打開後可以在windows\system32\logfiles裡找到類似IISLog的記錄檔

4.SMTP Svc使用上有沒有什麼限制?
A.先談談效能,在我的經驗中,網站整合SMTP Svc,對寄寄通知函、定期送報表等工作向來都勝任愉快。只有當年.COM泡沬化的前夕曾打算用它來發數萬封網路行銷電子報,一次硬塞數百封Mail會讓SMTP Svc不支倒地了... 不過,一般而言,專業電子報派發應該就近Colocation在ISP,更常見的作法是使用專門的軟體、平台派發,這種土法鍊鋼惡搞法對SMTP Svc而言本來就不太公平。
至於其他限制方面,由於SMTP Svc會查DNS,自行用25 Port連上對方的Mail Server交寄郵件,許多公司的防火牆根本不允許一般的PC連線Internet上的25 Port,要用得另外申請;另一方面,收信Mail Server也常會透過反查發信主機的DNS來過濾來路不明的SMTP Server,若SMTP Svc沒有設好DNS,也會吃閉門羮。對於這方面的困擾,我常用的一招是設定Smart Host! 設定Smart Host後,SMTP Svc就不必再抛頭露臉,所有的信都會轉交Smart Host主機,由它負責寄出。由於近來各公司都啟動了相關的垃圾信機制,因此透過公司統一的對外管道寄信問題較少。關於Smart Host的設定可以參考剛才提過的一篇KB

5.SMTP Svc有沒有什麼資安防護機制?
A.SMTP Svc可以限定接受連線的對象IP、允許Mail Relay的IP、加上SSL、TLS加密功能,還可要求使用者必須登入AD帳號後才可使用(跟IIS一樣可以選Basic、NT整合式認證等),透過這些設定,可以減少被當成垃圾信跳板的可能。另外,先前提到的Log功能則可以作為事後追查之用,也算是資安機制的一部分吧!

Posted 22 June 2007 08:10 AM by Jeffrey | 1 comment(s)
Filed under: ,
KB-ScriptTimeout! Then?

"當ScriptTimeout發生時,ASPX會繼續執行? 還是嘎然而止?"

以上問題的答案將牽動IIS的調校哲學,當系統發生問題時而導致大量Request處理逾時,調整ScriptTimeout的長短,會產生何種效應?

在過去的印象中,如果使用者開啟一個要執行很久的ASP/ASPX程式,在程式未執行完成之前,使用者關閉瀏覽器時,ASPX仍會繼續跑完,不受與使用者間連線中斷的影響。我用以下的Code進行驗證...

   1:  private void writeLog(string msg)
   2:  {
   3:      using (System.IO.StreamWriter sw =
   4:          new System.IO.StreamWriter("d:\\temp\\longrun.log", true))
   5:      {
   6:          sw.WriteLine(
   7:              string.Format("{0:yyyy-MM-dd HH:mm:ss.fff} {1}",
   8:              DateTime.Now, msg
   9:          ));
  10:      }
  11:   
  12:  }
  13:  protected void Page_Load(object sender, EventArgs e)
  14:  {
  15:      //Write start log
  16:      writeLog("Start!");
  17:      //Wait for 20 seconds
  18:      System.Threading.Thread.Sleep(20000);
  19:      //Write end log
  20:      writeLog("Finished!");
  21:  }

測試時,開啟IE連上該網頁後立即將IE關閉。20秒後檢查longrun.log,可以看到相隔20秒的兩列Log。於是我們得到證明: 使用者關閉Browser並不妨礙ASPX繼續執行

2007-06-21 04:20:51.814 Start!
2007-06-21 04:21:11.832 Finished!

那麼,當ScriptTimeout發生時又是如何? 我將web.config <httpRuntime> executionTimeout設為5秒,跑了以下的程式:

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:      //Write start log
   4:      writeLog("Start!");
   5:      for (int i = 0; i < 30; i++)
   6:      {
   7:          System.Threading.Thread.Sleep(1000);
   8:          //One log per second
   9:          writeLog("Loop-" + i.ToString());
  10:      }
  11:      //Write end log
  12:      writeLog("Finished!");
  13:      //Response.Write something    
  14:      Response.Write("Done!");
  15:      Response.End();
  16:  }

大約5-15秒左右(依據這串討論中MS Online Support的說法,ScriptTimeout小於1分鐘,會有5-15秒的延遲),網頁傳回以下的錯誤:
[HttpException (0x80004005): Request timed out.]

在Log則看到有趣的事,30圈的Loop沒有跑完,在i==5時停住了。

2007-06-21 04:26:50.561 Start!
2007-06-21 04:26:51.561 Loop-0
2007-06-21 04:26:52.561 Loop-1
2007-06-21 04:26:53.561 Loop-2
2007-06-21 04:26:54.561 Loop-3
2007-06-21 04:26:55.561 Loop-4
2007-06-21 04:26:56.561 Loop-5

不過我們還不滿足,希望得到更明確的ScriptTimeout證據。但,程式都中止了,還能再做些什麼事? 之前談過Response.End時產生的ThreadAbortException可以被catch抓到,而我猜ScriptTimeout所引發的中止應該也可以被catch捕捉下來。於是改寫Code如下:

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:      //Write start log
   4:      writeLog("Start!");    
   5:      try
   6:      {
   7:          for (int i = 0; i < 30; i++)
   8:          {
   9:              System.Threading.Thread.Sleep(1000);
  10:              writeLog("Loop-" + i.ToString());
  11:          }
  12:      }
  13:      catch (Exception ex)
  14:      {
  15:          writeLog("ERROR: " + ex.Message);
  16:      }
  17:      //Write end log
  18:      writeLog("Finished!");
  19:      //Response.Write something    
  20:      Response.Write("Done!");
  21:      Response.End();
  22:  }

Log結果如下:

2007-06-21 04:40:37.580 Start!
2007-06-21 04:40:38.580 Loop-0
2007-06-21 04:40:39.580 Loop-1
2007-06-21 04:40:40.580 Loop-2
2007-06-21 04:40:41.580 Loop-3
2007-06-21 04:40:42.580 Loop-4
2007-06-21 04:40:43.580 Loop-5
2007-06-21 04:40:44.580 Loop-6
2007-06-21 04:40:45.580 Loop-7
2007-06-21 04:40:46.580 Loop-8
2007-06-21 04:40:47.580 Loop-9
2007-06-21 04:40:48.580 Loop-10
2007-06-21 04:40:49.580 Loop-11
2007-06-21 04:40:50.002 ERROR: Thread was being aborted.

抓到了!! cath (Exception ex)抓到了ThreadAbortException。由這項證據可以得知,當程式執行時間超過ScriptTimeout時,W3WP.exe應該是用類似Response.End()的方式強制結束ASPX網頁的執行。

由以上的觀察,我的個人推論是--當程式異常時,設定較短的ScriptTimeout應有助於改善大量Thread被Hang住的狀況,盡早釋放出Resource處理其他仍然健全的功能。但如果程式是卡在Unmanaged World Resource,Thread.Abort並不能讓程式立即由困境中脫身(只能先在Thread上做記號,待控制權交回Managed World時才生效)。另外,以下兩則延伸閱讀中提到Multithread下Thread.Abort可能產生的負面效應,可做為進階設計時的考量。

延伸閱讀:
1.How To Stop a Thread in .NET (and Why Thread.Abort is Evil)
2.Plumbing the Depths of the ThreadAbortException Using Rotor

Posted 21 June 2007 06:55 AM by Jeffrey | no comments
Filed under: ,
TIPS-Server.ScriptTimeout & <httpRuntime executionTimeout >

為了進行一些效能實驗,我試著要將ScriptTimeout的時間縮短,並故意用Thread.Sleep來製造Timeout。

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:      //Set ScriptTimeout = 5 sec
   4:      Server.ScriptTimeout = 5;
   5:      //Sleep 20 seconds
   6:      System.Threading.Thread.Sleep(20000);
   7:      //Write log
   8:      using (System.IO.StreamWriter sw = 
   9:          new System.IO.StreamWriter("d:\\temp\\bench.log", true))
  10:      {
  11:          sw.WriteLine(
  12:              string.Format("{0:yyyy-MM-dd HH:mm:ss.fff}",
  13:              DateTime.Now
  14:          ));
  15:          sw.Close();
  16:      }
  17:      Response.Write("Done!");
  18:      Response.End();
  19:  }

 測試發現,將Server.ScriptTimeout設成5秒無法造成Timeout Exception,後來我又試著修改web.config的<httpRuntime> executionTimeout Attribute,仍然無效。

Google到這篇文章,MS Online Support給了頗為詳細的說明,解開了謎團:

1) <compliation debug="true" />時,ScriptTimeout設定會被忽略。
2) 當Timeout小於1分鐘時,實際上將Delay 5-15秒,也就是說executionTimeout=5,實際上可能要15秒才算Timeout。
3) Server.ScriptTimeout是ASP時代的遺跡,屬於COM Interface,不建議使用,在ASP.NET中要設定Timeout時間請改用web.config 的<httpRuntime> executionTimeout屬性。

More Posts Next page »

Search

Go

<June 2007>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
 
RSS
【工商服務】