先舉一個例子,以幫忙回憶起對多線程的印象。CopyFilesProc是實現拷貝文件的一個方法,用多線程調用:
Thread simpleThread = new Thread(CopyFilesProc);
simpleThread.Name = "CopyFiles";
simpleThread.Start();
啟動調用,在VS2010中,增加了線程調試窗口,以查看當前進程的線程。我的理解是,總是記得給你的線程命名。
如圖,月結的界面效果圖,當用戶點擊Process按鈕後,出現進度條,顯示處理進度
對於BackgroundWorker控件的運用,請查閱MSDN知識庫。我這裡對報告進度這一小功能,作說明。
報告進度的功能,分兩種情況來實現。一種是處理任務(Job)沒有用接口實現,當前進度變量存在於窗體中,源碼如下
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
bgWorker.ReportProgress(10, fileName);
}
直接在DoWork事件中,調用ReportProgress方法,如果可能,還可以傳入變量值
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string fileName = e.UserState.ToString();
}
注冊事件ProgressChanged來輸出當前正在處理的項目,在這個方法中可以操作UI控件。
另一種方法是,處理任務的實現(Job的功能實現)被隔離到第三方的類庫中,說白了就是放到外部的類文件中,不與窗體代碼混淆在一起。代碼舉例,對於月結功能,可能的一個這樣的實現
public interface IJob
{
void Execute();
}
實現文件
public class Job : IJob
{
public void Execute()
{
}
}
在窗體的bgWorker_DoWork方法中,調用實現job.Execute()來這現月結功能。
這種方法下,報告進度的功能稍微復雜一點,要用到定時器,進度存放變量值,執行任務時更新進度。
實現步驟如下,在接口實現文件,定義存放進度變量的集合值_JobProcess變量,
public class Job : IJob
{
private static ConcurrentDictionary<int, bool> _JobProcess;
在執行長時間任務前,初始化進度變量
public void Execute()
{
if (_JobProcess == null || _JobProcess.Count== 0||_JobProcess.Count>0)
{
_JobProcess = new ConcurrentDictionary<int, bool>();
for (int i = 1; i <= _Step; i++)
{
_JobProcess.TryAdd(i, false);
}
}
_Step是步長,這裡有簡化處理。實際操作中,要對任務進行分類處理,以達到分步的目的。比如,核算客戶往來帳目,就以客戶為依據來分步長,有121個客戶,這裡_Step就是121,然後每處理完一個客戶的帳目,就把它設置為已經處理,即這樣來調用
for (int i = 1; i <= _Step; i++)
{
_JobProcess[i] = true;
Thread.Sleep(2000);
}
每處理一個任務,就把它的時度標志為true,以表示已經處理。這樣,在進度反饋接口中,可以及時反饋結果
public object[] GetExecuteStatus()
{
//已經處理完成
int completedCount = (from item in _JobProcess
where item.Value == true
select item).Count();
//當前總任務數量
int totalCount = (from item in _JobProcess
select item).Count();
//正在處理的任務
int itemInProcess = (from item in _JobProcess
where item.Value == false
orderby item.Key
select item.Key).FirstOrDefault();
}
將這三個值返回到界面的timer的Tick事件中,實時改變界面的ProgressBar的狀態,以達到報告進度的目的。
當完成功能需要一定的時間,一般定為超過20second,都應該用BackgroundWorker來處理,以保持界面及時響應。
這個模式最主要的實現類是WorkerThreadBase,
public abstract class WorkerThreadBase : IDisposable
{
private Thread _workerThread;
private ManualResetEvent _stopping;
private ManualResetEvent _stopped;
private bool _disposed;
private bool _disposing;
使用了ManualResetEvent來同步多個線程,MSDN中對ManualResetEvent的解釋是:通知一個或多個正在等待的線程已發生事件. 如果不能明白它的意思,就跑一下測試代碼,來看看用途
DummyWorker dummyWorker = new DummyWorker();
dummyWorker.Start();
CopyFileWorker copyFileWorker = new CopyFileWorker(_copyInfo);
copyFileWorker.Start();
//wait for the two threads to finish
WorkerThreadBase.WaitAll(copyFileWorker, dummyWorker);
如代碼所示,創建2個工作線程,工作線程的創建方法如下
public class DummyWorker:WorkerThreadBase
{
protected override void Work()
{
}
}
繼承於WorkerThreadBase類型,重寫Work方法即可。
當調用WorkerThreadBase.WaitAll停止當前線程,等待所有的線程運行完畢後,然後可以顯示結果。
總結,後一種模式,編程簡單,效率也高一些;在WinForm應用中,也常常在Forms類型的方法中,動態創建BackgroundWorker然後調用它,以保持UI繼續接受用戶輸入。