我們在采集特定數據的時候,往往需要耗費較長的時間,有時候因為一些事情,不可能長久的在電腦前等待結果,那么需要程序在一段時間后自動給我們發送郵件等通知,以及執行退出程序或者關機等處理善后工作,以節省資源或者電源,那么需要實現這個過程是如何的呢。本篇隨筆基於這個采集程序的基礎上增加這些功能的實現,介紹其中的一些處理技巧。
1、郵件配置
如果我們需要實現發送郵件、或者發送短信等通知途徑,那么我們就需要把這些處理過程涉及到的參數提前錄入到系統里面,是在不行硬編碼也行,不過為了可擴展性,我傾向於使用配置界面進行參數的配置。
在關於參數配置的處理,我在博客《Winform開發框架之參數配置管理功能實現-基於SettingsProvider.net的構建》以及《Winform界面中實現菜單列表的動態個性化配置管理》都做了比較詳細的介紹,基於SettingsProvider.net的封裝處理,能夠實現我們很方便的配置功能,可以配置在XML文件中,也可以保存在數據庫中,根據需要處理。
那么我這里為了采集發送數據的需要,也需要配置一個郵件的信息,如下界面所示。
這個里面放置額外的兩個功能按鈕,一個是郵箱設置參考,一個是發送測試郵件,前者用來輔助填入一些參數,后者是驗證用戶賬號是否收到測試郵件。
發送測試郵件成功后,我們驗證下是否收到,以便核對下提供的參數是否正確。
2、設置定時處理
完成上面的步驟后,我們基本上完成了一半的工作量了,剩下的就是在合適的時間,讓系統發送通知給我們以及善后處理即可。
那么我們如果定時的話,需要指定一個時間范圍,使用DevExpress的TimeSpanEdit控件就合適不過了,我們只需要確定小時:分鍾:秒的數據后,就可以根據這個時間范圍確定我們執行任務的最終時間了。
這個彈出的小窗體,我們只用來獲取用戶輸入的時間范圍即可,沒有什么具體的邏輯。
輸入關機時間后,那么我們就可以根據關機時間,彈出一個倒計時的窗體,覆蓋在主程序的界面上。
最終我們到達時間的觸發點后,實現發送郵件通知以及退出程序或者關機的處理。
以上是整個處理的過程,那么實現的處理代碼是如何的呢,我們來分析下具體的代碼過程。
private bool isShutdown = false; private TimerHelper timerHelper; private void btnSendAndShutdown_Click(object sender, EventArgs e) { btnSendAndShutdown.Enabled = false; try { if (!isShutdown) { //獲取關機的時間 FrmShutdownTime frmTime = new FrmShutdownTime(); if (frmTime.ShowDialog() != System.Windows.Forms.DialogResult.OK) return; //轉換為最終的時間 TimeSpan timeSpan = frmTime.timeShutdown.TimeSpan; this.endTime = DateTime.Now.Add(timeSpan); //定時器輔助類處理定時工作 timerHelper = new TimerHelper(1000, true); timerHelper.Execute += () => { //每隔一秒對事件進行處理判斷 if (ShutdownEvent != null) { ShutdownEvent(sender, e); } }; //顯示關機面板 groupShutdown.BringToFront(); groupShutdown.Visible = true; } else { //關閉面板 CloseShutDownGroup(); } isShutdown = !isShutdown; this.btnSendAndShutdown.Text = isShutdown ? "取消關機處理" : "定時發送郵件后關機"; } finally { btnSendAndShutdown.Enabled = true; } }
上面主要的處理邏輯,放在了定時器的處理事件上
ShutdownEvent(sender, e);
這個觸發的事件,是我們在主窗體定義的一個事件,目的就是用來實現倒計時及發送通知用的。
private event EventHandler ShutdownEvent;
然后我們在窗體里面初始化這個事件處理即可,初始化代碼如下所示。
//關閉或者退出程序的事件 this.ShutdownEvent += (s, e)=> { #region 定時處理操作 this.Invoke(new MethodInvoker(delegate() { //判斷當前的剩余時間是否進入通知流程 var ts = endTime.Subtract(DateTime.Now); if (ts.TotalSeconds > 1) { //更新倒計時 timeLeft.TimeSpan = new TimeSpan(ts.Days, ts.Hours, ts.Minutes, ts.Seconds); } else { //關閉面板並退出定時器 CloseShutDownGroup(); //執行發送郵件操作 SendMail(); //關閉主機或者退出程序 if (chkShutdown.Checked) { Process.Start("shutdown.exe", "-s");//關機 } else if (chkExitApp.Checked) { Application.ExitThread(); } } })); #endregion
最終的邏輯就是發送郵件和退出程序或者關機的處理
//執行發送郵件操作 SendMail(); //關閉主機或者退出程序 if (chkShutdown.Checked) { Process.Start("shutdown.exe", "-s");//關機 } else if (chkExitApp.Checked) { Application.ExitThread(); }
關機的操作,我們用來執行命令行的方式實現關機的處理,非常方便。
關於這個Shutdown命令的處理,我下面列出它的一些功能說明。
shutdown命令的參數: shutdown.exe -s:關機 shutdown.exe -r:關機並重啟 shutdown.exe -l:注銷當前用戶 shutdown.exe -s -t 時間:設置關機倒計時 shutdown.exe -h:休眠 shutdown.exe -t 時間:設置關機倒計時。默認值是 30 秒。 shutdown.exe -a:取消關機 shutdown.exe -f:強行關閉應用程序而沒有警告 shutdown.exe -m \計算機名:控制遠程計算機 shutdown.exe -i:顯示“遠程關機”圖形用戶界面,但必須是Shutdown的第一個參數 shutdown.exe -c "消息內容":輸入關機對話框中的消息內容 shutdown.exe -d [u][p]:xx:yy :列出系統關閉的原因代碼:u 是用戶代碼 ,p 是一個計划的關閉代碼 ,xx 是一個主要原因代碼(小於 256 的正整數) ,yy 是一個次要原因代碼(小於 65536 的正整數) 比如你的電腦要在12:00關機,可以選擇“開始→運行”,輸入“at 12:00 Shutdown -s",這樣,到了12點電腦就會出現“系統關機”對話框,默認有30秒鍾的倒計時並提示你保存工作。 如果你想以倒計時的方式關機,可以輸入 “Shutdown.exe -s -t 3600",這里表示60分鍾后自動關機,“3600"代表60分鍾。
而發送郵件,我們一般利用一個郵件發送的封裝類處理下即可。
/// <summary> /// 發送郵件 /// </summary> private bool SendMail() { //統計數據 btnSumaryData_Click(null, null); string title = string.Format("{0}期統計結果-{1}", Portal.gc.CurrentQSNumber, DateTime.Now); //獲取控件展示的內容,並把它的換行轉換下 List<string> list = new List<string>(); list.AddRange(txtShenxiao.Lines); list.AddRange(this.txtShenxiao2.Lines); string content = string.Join("<br>", list); //發送郵件 var success = SendMailHelper.SendMail(title, content); return success; }
發送郵件的時候,我們先獲取用戶的郵件配置信息,然后調用郵件發送輔助類實現內容的發送處理,具體代碼如下所示。
public class SendMailHelper { /// <summary> /// 發送郵件結果 /// </summary> public static bool SendMail(string title, string content) { //獲取配置的郵件信息 string creator = "";// LoginUserInfo.Name; ISettingsStorage store = new DatabaseStorage(creator); SettingsProvider settings = new SettingsProvider(store); bool result = false; EmailParameter parameter = settings.GetSettings<EmailParameter>(); if (parameter != null) { //使用郵件輔助類,實現郵件內容的發送 EmailHelper email = new EmailHelper(parameter.SmtpServer, parameter.LoginId, parameter.Password); email.Subject = title; email.Body = content; email.IsHtml = true; email.Charset = "gb2312"; email.From = parameter.Email; email.FromName = parameter.Email; email.AddRecipient(parameter.Email); result = email.SendEmail(); } return result; }
以上就是整個處理的過程,其中還涉及到了在線程間訪問控件的方式,如下代碼所示。
這個詳細的介紹可以參考我較早期的隨筆《淺談多線程中數據的綁定和賦值》,這些細節都需要我們一步步測試並尋找最佳的方案實現,希望這個隨筆的思路給你有一定的啟發。
當然,如果系統做的比較大一些,系統化一些的話,還可以考慮利用EasyNetQ的這種方式實現信息的通知。
這種通知可以更好的擴展,詳細介紹可以參考下隨筆《使用EasyNetQ組件操作RabbitMQ消息隊列服務》,不過一般小程序的就不用那么麻煩了,用一個定時器來處理下就可以了。