2010-03-17 78 views
6

我正在嘗試編寫多線程代碼並面臨一些同步問題。我知道這裏有很多帖子,但我找不到合適的東西。同步線程 - 沒有UI

我有一個System.Timers.Timer每30秒過去它會去分貝,並檢查是否有任何新的工作。如果他找到一個,他會在當前線程上執行該任務(定時器會爲每個已經過的時間打開新線程)。在作業運行時,我需要通知主線程(計時器的位置)關於進度。

注:

  1. 我沒有UI,所以我不能做beginInvoke(或使用後臺線程),因爲我通常的WinForms做。
  2. 我想在我的主課上實施ISynchronizeInvoke,但看起來有點矯枉過正(也許我在這裏錯了)。
  3. 我在我的工作類中有一個事件,並且主類註冊了它,每當我需要時我都會調用事件,但是我擔心它可能會導致阻塞。
  4. 每項工作可能需要長達20分鐘。
  5. 我最多可以同時運行20個作業。

我的問題是:

什麼是通知有關我的工作線程的任何進展我的主線程的正確方法?

感謝您的任何幫助。

+0

不能通知線程。你可以通知一個對象。更好地描述這個部分,你可能會得到更好的答案。 – 2010-03-17 16:55:35

回答

1

使用事件。例如,BackgroundWorker類是專門爲您設計的。

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

ProgressChanged事件沿ReportProgress功能,你會用什麼最新進展。

pullJobTimer.Elapsed += (sender,e) => 
{ 
    BackgroundWorker worker = new BackgroundWorker(); 
    worker.WorkerReportsProgress = true; 
    worker.DoWork += (s,e) => 
    { 
     // Whatever tasks you want to do 
     // worker.ReportProgress(percentComplete); 
    }; 
    worker.ProgressChanged += mainThread.ProgressChangedEventHandler; 
    worker.RunWorkerAsync(); 
}; 
+0

我覺得使用BackgroundWorker是錯誤的,我可以解釋原因。 System.Timers.Timer pullJobTimer = new System.Timers.Timer(INTERVAL); pullJobTimer.Elapsed + = new System.Timers.ElapsedEventHandler(PullQJobs); 我可以在pulljobs中打開一個新的BackgroundWorker,這是真的,然後我會有ProgressChanged。但計時器過去了打開一個新的線程,然後我會打開一個新的backgroundworker線程?感覺不對?或者,也許我沒有明白你的想法。 – UshaP 2010-03-17 15:33:49

+0

你甚至不需要打開一個新的線程。當你通過'RunWorkerAsync()'異步運行它時,BackgroundWorker會爲你做。正如Jim Mischel所建議的那樣,這也不會導致內存泄漏,因爲事件訂閱在垃圾回收時會被BackgroundWorker實例破壞。 – Jake 2010-03-17 15:43:20

+0

我知道backgroundworker打開一個線程,但也是計時器。 因此,主類使用計時器 - >計時器打開一個新的線程爲每個過去 - >然後我運行backgroundworker打開另一個線程。 這就是讓我感到不舒服的原因。 – UshaP 2010-03-17 16:39:59

1

您可以通過回調方法通知進度主線程。那就是:

// in the main thread 
public void ProgressCallback(int jobNumber, int status) 
{ 
    // handle notification 
} 

您可以傳遞一個回調方法的工作線程,當你調用它(即作爲代表),或者工作線程的代碼可以「知道」它含蓄。無論哪種方式工作。

jobNumber和status參數就是例子。您可能希望使用其他方式來識別正在運行的作業,並且您可能需要爲枚舉狀態使用枚舉類型。但是,您應該知道,ProgressCallback將由多個線程同時調用,因此如果您要更新任何共享數據結構或編寫日誌記錄信息,則必須使用鎖定或其他同步技術來保護這些資源。

您也可以爲此使用事件,但保持主線程的事件訂閱是最新的可能是一個潛在的問題。如果您忘記取消訂閱特定工作線程事件的主線程,也可能會發生內存泄漏。雖然事件肯定會起作用,但我會建議此應用程序的回調。

+0

Jim,我認爲這是同步調用,而我需要異步調用 – UshaP 2010-03-17 17:53:45

+0

進程回調在工作線程上調用,而不是在主線程上調用。因此,從主線程的角度來看,這是一個異步調用。 – 2010-03-23 15:37:08

1

如果您不介意取決於.NET 3.0,則可以使用Dispatcher來封送線程之間的請求。它的行爲與Windows窗體中的Control.Invoke()類似,但沒有Forms依賴關係。您需要添加一個對WindowsBase程序集的引用,雖然(.NET 3.0和更新的一部分,是WPF的基礎)

如果你不能依賴於.NET 3.0,那麼我會說你是從一開始就進入正確的解決方案:在主類中實現ISynchronizeInvoke接口,並將其傳遞給定時器的SynchronizingObject屬性。然後你的計時器回調將在主線程上調用,然後可以產生BackgroundWorkers來檢查數據庫並運行任何排隊的作業。作業將通過ProgressChanged事件報告進度,該事件將自動將調用編組到主線程。

快速谷歌搜索顯示this example關於如何實際實現ISynchronizeInvoke接口。

2

您也可以使用lock來實現線程安全的JobManager類,該類追蹤不同工作線程的進度。在這個例子中,我只是維護活動工作線程數量,但這可以擴展到您的進度報告需求。

class JobManager 
{ 
    private object synchObject = new object(); 

    private int _ActiveJobCount; 

    public int ActiveJobsCount 
    { 
     get { lock (this.synchObject) { return _ActiveJobCount; } } 
     set { lock (this.synchObject) { _ActiveJobCount = value; } } 
    } 

    public void Start(Action job) 
    { 
     var timer = new System.Timers.Timer(1000); 

     timer.Elapsed += (sender, e) => 
     { 
      this.ActiveJobsCount++; 
      job(); 
      this.ActiveJobsCount--; 
     }; 

     timer.Start(); 
    } 
} 

例子:

class Program 
{ 
    public static void Main(string[] args) 
    { 
     var manager = new JobManager(); 

     manager.Start(() => Thread.Sleep(3500)); 

     while (true) 
     { 
      Console.WriteLine(manager.ActiveJobsCount); 

      Thread.Sleep(250); 
     } 
    } 
}