2008-09-23 66 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()