2012年1月23日 星期一

C# Threading - 使用 WaitHandle

上一篇文章(C# 多緒程式該怎麼寫? - Threading)說到,
副執行緒還再跑的時候按鈕卻已經浮起來了,
然後總執行時間也不太對,
因為那個總執行時間並不包含副執行緒跑完的時間!
沒想到這麼快我就找到了解決的方法了!
誤打誤撞的找到了 WaitHandle 的範例,
於是就照著範例改了一下程式,
範例如下:
先宣告 WaitHandle 的陣列

    // Define an array with two AutoResetEvent WaitHandles.
    static WaitHandle[] waitHandles = new WaitHandle[]
    {
        new AutoResetEvent(false),
        new AutoResetEvent(false)
    };

然後使用方式如下:

ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask),         
        waitHandles[0]);
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), 
        waitHandles[1]);
WaitHandle.WaitAll(waitHandles);

然後在副執行緒的最後要補上一行,
將傳進來的 AutoResetEvent 呼叫 Set(),
範例上是這樣寫的,如下:

are.Set();

看起來好像不難,所以我就先照著改一下囉!

改完執行程式卻有錯誤跑出來!?!?


STA?好熟悉的縮寫,
STA 是 Single-Threaded Apartment 的縮寫,
查了一下,原來是在 Program.cs 裡面都會有的那個標籤丫!



我明明就是要寫多緒的程式咧,
怎麼可以用 STA 咧!
不過看起來似乎只有在執行緒的同步的時候才會有錯誤出現;
修改的方式很簡單,改成如下:

//[STAThread]
[MTAThread]
static void Main()

然後執行了一下,程式沒有回應了!
一步一步跟著 trace ,發現原來程式停在 this.Invoke 的地方,

this.Invoke(new UpdateLabelTextHandler(UpdateLabelText),
    new object[] { count, dt.Rows.Count.ToString("N") });

想了一下,因為 Invoke 是呼叫主執行緒來更新介面上的屬性,
但是現在主執行緒在等副執行緒呼叫 Set() 才會繼續執行,
結果就變成大家都在等等等,
想了一下,看來只能把 are.Set() 往上搬到 are.Set() 的下面了!
程式修改如下:

private void QueryTableByThreading(object param)
{
    object[] oa = (object[])param;

    string table = oa[0].ToString();
    Label count = (Label)oa[1];
    Label duration = (Label)oa[2];
    CalcTime ct = (CalcTime)oa[3];
    AutoResetEvent are = (AutoResetEvent)oa[4];

    DataTable dt = CommonDBUtility.GetTableFromDB(table);

    string time = ct.Duration();

    are.Set();

    this.Invoke(new UpdateLabelTextHandler(UpdateLabelText),
        new object[] { count, dt.Rows.Count.ToString("N") });

    this.Invoke(new UpdateLabelTextHandler(UpdateLabelText),
        new object[] { duration, time });

    //are.Set();
}

這樣改完之後程式就可以正常執行了,
然後按鈕跟時間也都對了!

看(絕對沒有罵人的意思)!這個時候按鈕還沒浮起來!
所以主執行緒真的有在等副執行緒跑完。不過程式沒有回應?


全部做完的時候按鈕才會浮起來,然後整個查詢的時間也對了!
不在是上一個範例那個豪洨的 1 毫秒了

不過我還是不太滿意說,怎麼說咧?
因為依照 VS 的範例程式必須寫死,
個人生平第二討厭的就是程式寫死了!
(第一討厭的是要把死的變活的!)
所以我就是著改寫看看,改寫的程式如下:

/* WaitHandle[] waitHandles = new WaitHandle[] {
    new AutoResetEvent(false), new AutoResetEvent(false) }; */
List<WaitHandle> waitHandles = new List<WaitHandle>();

private void btnThreadingQuery_Click(object sender, EventArgs e)
{
    Cursor tmpCursor = this.Cursor;
    Label[] labels = { lblThreadingDuration, lblThreadingTable1Count,
                         lblThreadingTable1Duration,
                         lblThreadingTable2Count,
                         lblThreadingTable2Duration };
    ResetLabel(labels);
    Application.DoEvents();

    try
    {
        btnThreadingQuery.Enabled = false;
        this.Cursor = Cursors.WaitCursor;

        CalcTime calcTime = new CalcTime();

        waitHandles.Add(new AutoResetEvent(false));
        object param1 = new object[] { "table1",
            lblThreadingTable1Count,
            lblThreadingTable1Duration, calcTime,
            waitHandles[waitHandles.Count()-1]};

        ThreadPool.QueueUserWorkItem(
            new WaitCallback(QueryTableByThreading), param1);

        waitHandles.Add(new AutoResetEvent(false));
        object param2 = new object[] { "table2",
            lblThreadingTable2Count,
            lblThreadingTable2Duration, calcTime,
            waitHandles[waitHandles.Count()-1]};

        ThreadPool.QueueUserWorkItem(
            new WaitCallback(QueryTableByThreading), param2);

        WaitHandle.WaitAll(waitHandles.ToArray());

        lblThreadingDuration.Text = calcTime.Duration();
    }
    finally
    {
        this.Cursor = tmpCursor;
        btnThreadingQuery.Enabled = true;
    }
}

我用 List<WaitHandle> 來取代 WaitHandle[] 的宣告,
然後要呼叫 WaitAll 的時候將 waitHandles.ToArray() ,
跑起來看似沒有問題,但是跑第二次的時候又沒有回應了?
想了一下,於是乎我在 btnThreadingQuery_Click 第一行加上一行程式:

waitHandles = new List<WaitHandle>();

這樣就沒有問題了,
不過因為主執行緒要等副執行緒查完之後才會更新介面上的資訊感覺很鳥,
我想要查詢跑完之後就馬上顯示資訊給我看,
看來還需要在修正修正,
而且視窗上面顯示沒有回應真的很鳥ㄟ!
所以主執行緒應該就讓它專心做介面相關的東西就好了,
下一篇再來說說我怎麼改的好了,
過年不要寫太多!

沒有留言:

張貼留言