2009年10月14日 星期三

在C#中使用Mutex

在多執行緒(Multi-Thread)程式中
執行緒之間常常會有相依性,所以需要一些指標
讓搶到指標的人才能存取某個資源或是執行動作
這時候就會需要用到Mutex

Mutex的使用其實非常簡單

/// new a mutex with name
Mutex m = new Mutex(false, "m");
m.WaitOne();

如果要釋放這個資源讓別人使用的時候

m.ReleaseMutex();

不過這邊有很多需要注意的地方
首先,Mutex分為有名字及無名兩種
在自己程式中可以使用無名的Mutex
但是有命名的Mutex會在程式間也有效
所以可以做出讓程式無法重覆啟動的檢查
例如在wpf程式中增加應用程式的建構子(constructor)

/// declare mutex in class to last it until app end
Mutex m = new Mutex(false, "m");
App()
{
/// if the mutex exists, return false in 1 ms
if (!m.WaitOne(1))
{
MessageBox.Show("program is current running!");
Shutdown();
}
}

這時候這支程式就沒辦法重覆啟動了
但是這時候要注意到第二個重點了
因為C#會自動作回收(garbage collection)的動作
如果Mutex是宣告在建構子裡面,那在建構成功後就會被回收了
所以即使沒有釋放(ReleaseMutex)依舊會自動失效
也就是沒辦法阻止重覆啟動

第三個重點就是WaitOne的等待時間了
如果裡面沒有任何的參數,那就是直接等待到有空為止
這有可能會讓程式直接停住
所以最簡單的做法就是增加一個很短的等待時間
並在回傳失敗的地方先做一些處理(比如等待的警告之類的)
之後就放心大膽的等下去~~

另外,如果是有命名的Mutex
那會跟Win32的CreateMutex有相同的效果
也就是兩者使用相同命名的時候也會互相等待

--
參考資料
Threading Tutorial Example 4: Using the Mutex object
Mutex 類別

2009年10月12日 星期一

在WPF的XAML裡面使用動畫

在WPF裡面使用動畫可以參考在WPF中加入動畫
不過有時候動畫比較單純,而不需要非常精細的轉換計算動作的話
其實可以直接將動畫宣告在XAML裡面,讓他成為物件的附屬特性

<Image.Triggers>
<!--begin animation after loaded-->
<EventTrigger RoutedEvent="Image.Loaded">
<BeginStoryboard Name="bs">
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1" To="0"
Duration="0:0:0.5"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!--pause animation after leftbutton down-->
<EventTrigger
RoutedEvent="Image.MouseLeftButtonDown">
<PauseStoryboard BeginStoryboardName="bs" />
</EventTrigger>
<!--resume animation after leftbutton up-->
<EventTrigger
RoutedEvent="Image.MouseLeftButtonUp">
<ResumeStoryboard BeginStoryboardName="bs" />
</EventTrigger>
<!--stop animation after rightbutton up-->
<EventTrigger
RoutedEvent="Image.MouseRightButtonUp">
<StopStoryboard BeginStoryboardName="bs" />
</EventTrigger>
</Image.Triggers>

被設定上面屬性的影像(Image)會在載入之後就不斷的忽隱忽現

要在XAML裡面設定動畫要先確定是對哪個控制項
並新增該控制項的觸發器(Trigger),可分為幾類
觸發器說明
EventTrigger在指定事件發生時觸發
參考HOW TO:使用腳本建立屬性的動畫
Trigger在指定屬性值變更時觸發
參考HOW TO:當屬性值變更時觸發動畫
DataTrigger在指定資料變更時觸發
參考HOW TO:資料變更時觸發動畫

設定觸發器之後,選擇要開始撥放或是對已經在撥放的動畫做動作
比較常見的有幾種
腳本操作說明
BeginStoryboard開始播放腳本,並在裡面設定動畫的細節
PauseStoryboard暫停播放腳本
ResumeStoryboard繼續播放腳本
StopStoryboard結束播放腳本

動畫也一樣可以選擇種類,可以參考在WPF中加入動畫

--
參考資料
腳本概觀
動畫概觀
HOW TO:在腳本開始後使用事件觸發程式進行控制

2009年10月5日 星期一

在WPF中使用背景工作(BackgroundWorker)

在程式中有時候會需要執行一些比較花時間的工作
比如連上網路下載或上傳東西
但在和網路溝通及傳輸的時候,畫面上的工作就會暫停
這時候會想要提示使用者程式還在工作,比如說在msn登入的時候撥放動畫

這時候就要用到背景工作了,實際上這就是一種多執行緒處理
只是.net提供了方便的包裝,讓主執行緒在背景工作執行中得知進度
BackgroundWorker主要有三個事件處理
事件說明
DoWork在背景執行的工作,為另外一個執行緒,不可以變更介面畫面(介面變更在後面兩個事件中處理)
RunWorkerCompleted背景工作完成後的處理,因為背景工作是非同步處理,所以在完成後會觸發這事件來啟動主執行緒接下來的動作,如登入完成後執行切換畫面
ProgressChanged如果動作費時太長,有時候會希望將進度反應在介面上,在這裡改變介面畫面

我們來做一個範例(參考HOW TO:使用幕後背景工作)
視窗中只有一個按鈕(Button)

<Window ...>
<Button Name="b" Click="Button_Click" />
</Window>

在按鈕按下的時候開始設定並執行背景工作

private void Button_Click(
object sender, RoutedEventArgs e)
{
/// new and allow cancel and report progress
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;

/// add the event handler for each progress
bw.DoWork += new DoWorkEventHandler(DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(DuringWork);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(AfterWork);

/// start the background work
bw.RunWorkerAsync();
}

事件處理定義如下

void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker w = sender as BackgroundWorker;

for (int i = 1; i <= 10; i++)
{
/// check if the work is cancelled
if (w.CancellationPending == true)
{
e.Cancel = true;
break;
}

/// perform a time consuming operation
Thread.Sleep(500);

/// report progress in percentage
w.ReportProgress(i * 10);
}
}

void DuringWork(
object sender, ProgressChangedEventArgs e)
{
/// reflect the change of progress in UI
b.Content =
e.ProgressPercentage.ToString() + "%";
}

void AfterWork(
object sender, RunWorkerCompletedEventArgs e)
{
/// reflect the result after background work
if (e.Cancelled == true)
{
b.Content = "Canceled!";
}
else if (!(e.Error == null))
{
b.Content = ("Error: " + e.Error.Message);
}
else
{
b.Content = "Done!";
}
}

這個程式執行後,按下按鈕的時候
就會開始執行DoWork裡面的動作(每半秒更新一下進度)
並在每次更新進度的時候更新Button上面顯示的文字

另外,有時候背景程式需要使用到介面輸入的資訊
(比如登入畫面,需要將帳號密碼傳上網路)
但是DoWork裡面不能使用介面的物件
這時候可以透過argument(object形態,可以傳任意型別)傳入
所以啟動背景工作的呼叫改寫成

private void Button_Click(
object sender, RoutedEventArgs e)
{
/// new and allow cancel and report progress
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;

/// add the event handler for each progress
bw.DoWork += new DoWorkEventHandler(DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(DuringWork);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(AfterWork);

/// start the background work with argument
bw.RunWorkerAsync(b.Content);
}

而在DoWork裡面則是用e.Argument將傳入的參數讀出
如果有超過一個以上的參數,也可以利用ArrayList先打包起來再一起傳

--
參考資料
HOW TO:使用幕後背景工作
BackgroundWorker 類別