2008-09-23 64 views
10

什麼是正確的技術有的ThreadA信號ThreadB一些事件的,而不必ThreadB坐阻塞,等待一個事件的發生?.NET:如何獲得後臺線程信號主線程數據?

我有一個後臺線程將填寫共享列表<T>。我試圖找到一種方法來異步發信號通知有「數據可用於拾取」的「主」線程。


我認爲用的EventWaitHandle對象設置一個事件,但我不能有我的主線程坐在一個Event.WaitOne()。


我認爲有一個委託回調,但 一)我不想在主線程中的委託做的工作:在線程需要回去工作增加更多的東西 - 我不希望它等待代理執行,並且b)委託需要被編組到主線程中,但是我沒有運行UI,我沒有控制權來調用委託。


我認爲有,簡單地啓動一個零間隔System.Windows.Forms.Timer(與螺紋訪問計時器同步)的委託回調。這樣,線程只需要堅持,因爲它要求

Timer.Enabled = true;

,但似乎像一個黑客攻擊。

在過去的日子裏,我的對象會創建一個隱藏窗口,並讓線程發送消息給隱藏窗口的HWND。我認爲創建一個隱藏的控件,但我收集你不能。調用一個沒有創建句柄的控件。另外,我沒有用戶界面:我的對象可能已經在Web服務器,服務或控制檯上創建,我不希望出現圖形控件 - 也不想編譯對System.Windows的依賴關係。形式。


我認爲有我的對象暴露的ISynchronizeInvoke接口,但後來我需要實現.Invoke(),這是我的問題。


什麼是正確的技術有線程一個事件的信號線程B,沒有線程B坐着等待事件發生?

回答

10

下面是System.ComponentModel.BackgroundWorker類的代碼示例。

private static BackgroundWorker worker = new BackgroundWorker(); 
    static void Main(string[] args) 
    { 
     worker.DoWork += worker_DoWork; 
     worker.RunWorkerCompleted += worker_RunWorkerCompleted; 
     worker.ProgressChanged += worker_ProgressChanged; 
     worker.WorkerReportsProgress = true; 

     Console.WriteLine("Starting application."); 
     worker.RunWorkerAsync(); 

     Console.ReadKey(); 
    } 

    static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     Console.WriteLine("Progress."); 
    } 

    static void worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     Console.WriteLine("Starting doing some work now."); 

     for (int i = 0; i < 5; i++) 
     { 
      Thread.Sleep(1000); 
      worker.ReportProgress(i); 
     } 
    } 

    static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     Console.WriteLine("Done now."); 
    } 
1

如果使用backgroundworker啓動第二個線程並使用ProgressChanged事件通知其他線程數據已準備就緒。其他活動也可以。 THis MSDN article should get you started

+0

BackgroundWorker的類似乎是可以異步發送notificati唯一在創建該對象的* thread *上。它在內部通過調用asyncOperation.Post()來使用AsyncOperation對象。 – 2008-09-23 21:28:55

1

有很多方法可以做到這一點,具體取決於你想要做什麼。一個producer/consumer queue可能是你想要的。如需深入瞭解線索,請參閱優秀書C# 3.0 in a Nutshell中關於Threading(網上提供)的章節。

+0

在生產者/消費者模型中,主線程創建需要執行的工作單元。後臺線程正在等待工作排隊。創建工作單元並設置事件時。這不起作用,因爲主線是「消費者」,它不能坐等工作。 – 2008-09-23 19:18:57

1

您可以使用AutoResetEvent(或ManualResetEvent)。如果使用AutoResetEvent.WaitOne(0,false),則不會阻塞。例如:

AutoResetEvent ev = new AutoResetEvent(false); 
... 
if(ev.WaitOne(0, false)) { 
    // event happened 
} 
else { 
// do other stuff 
} 
+0

如何檢查事件是否有信號?即何時? – 2008-11-21 18:22:21

0

如果你的「主」線程是Windows消息泵(GUI)線程,那麼你就可以查詢使用Forms.Timer - 根據您需要多快的速度讓你的GUI調整計時器的時間間隔線程'通知'來自工作線程的數據。

如果您要使用foreach,請記住同步訪問共享的List<>,以避免CollectionModified異常。

我在實時交易應用程序中對所有市場數據驅動的圖形用戶界面更新使用此技術,並且它工作得非常好。

3

我在這裏合併了一些答案。

理想情況使用線程安全標誌,如AutoResetEvent。當您撥打WaitOne()時,您不必無限期地阻止,實際上它有一個允許您指定超時的重載。如果在間隔期間沒有設置標誌,則該過載返回false

A Queue對於生產者/消費者關係來說是更理想的結構,但是如果您的要求迫使您使用List,則可以模仿它。主要的區別是你將不得不確保你的消費者在提取物品的同時鎖定訪問權限;最安全的事情是可能使用CopyTo方法將所有元素複製到一個數組,然後釋放鎖。當然,確保您的製片人不會在鎖定時更新List

下面是一個簡單的C#控制檯應用程序,演示了這可能如何實現。如果你玩弄定時間隔,你可能會導致各種事情發生;在這個特定的配置中,我試圖在消費者檢查項目之前讓生產者生成多個項目。

using System; 
using System.Collections.Generic; 
using System.Threading; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     private static object LockObject = new Object(); 

     private static AutoResetEvent _flag; 
     private static Queue<int> _list; 

     static void Main(string[] args) 
     { 
      _list = new Queue<int>(); 
      _flag = new AutoResetEvent(false); 

      ThreadPool.QueueUserWorkItem(ProducerThread); 

      int itemCount = 0; 

      while (itemCount < 10) 
      { 
       if (_flag.WaitOne(0)) 
       { 
        // there was an item 
        lock (LockObject) 
        { 
         Console.WriteLine("Items in queue:"); 
         while (_list.Count > 0) 
         { 
          Console.WriteLine("Found item {0}.", _list.Dequeue()); 
          itemCount++; 
         } 
        } 
       } 
       else 
       { 
        Console.WriteLine("No items in queue."); 
        Thread.Sleep(125); 
       } 
      } 
     } 

     private static void ProducerThread(object state) 
     { 
      Random rng = new Random(); 

      Thread.Sleep(250); 

      for (int i = 0; i < 10; i++) 
      { 
       lock (LockObject) 
       { 
        _list.Enqueue(rng.Next(0, 100)); 
        _flag.Set(); 
        Thread.Sleep(rng.Next(0, 250)); 
       } 
      } 
     } 
    } 
} 

如果你根本不想阻止製片人,那會更棘手。在這種情況下,我建議讓製作人擁有私人和公共緩衝區以及公共的AutoResetEvent。生產者默認將項目存儲在私有緩衝區中,然後嘗試將它們寫入公共緩衝區。當消費者使用公共緩衝區時,它將重置生產者對象上的標誌。在生產者試圖將項目從私有緩衝區移動到公共緩衝區之前,它會檢查此標誌,並且只在消費者不使用它時複製項目。

+0

隊列看起來像一個有趣的類使用。雖然我不想在我的情況下,因爲有時用戶可以一次處理整個列表,而不是必須將單個項目出列。 – 2008-09-23 21:31:23

1

在這種情況下,BackgroundWorker類是回答。它是唯一能夠將消息異步發送到創建BackgroundWorker對象的線程的線程構造。內部BackgroundWorker通過調用asyncOperation.Post()方法使用AsyncOperation類。

this.asyncOperation = AsyncOperationManager.CreateOperation(null); 
this.asyncOperation.Post(delegateMethod, arg); 

一些其他類在.NET框架也使用AsyncOperation:

  • BackgroundWorker的
  • 聲音播放。LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DownloadFile()
  • WebClient.DownloadFileAsync()
  • Web客戶端...
  • PictureBox.LoadAsync()