.NET:如何使用后台线程信号主线程数据?

时间:2008-09-23 18:35:51

标签: .net multithreading asynchronous

某些事件的 ThreadA 信号 ThreadB 的正确技巧是什么,没有 ThreadB 被阻止等待事件发生?

我有一个后台线程,它将填充共享列表< T>。我正试图找到一种方法来异步发信号通知“主”线程,即有可用的数据被拾取。


我考虑使用EventWaitHandle对象设置一个事件,但我不能让我的主线程坐在Event.WaitOne()上。


我考虑过委托回调,但是 a)我不希望主线程在委托中工作:线程需要重新开始工作添加更多东西 - 我不希望它在委托执行时等待,并且 b)委托需要编组到主线程上,但我没有运行UI,我没有控制权。禁止委托。


我认为有一个委托回调,它只是启动零间隔System.Windows.Forms.Timer(同步的线程访问定时器)。这样线程只需要在调用时卡住

Timer.Enabled = true;

但这似乎是一个黑客。

在过去的日子里,我的对象会创建一个隐藏的窗口并让线程将消息发送到隐藏的窗口'HWND。我考虑创建一个隐藏的控件,但我认为你不能。在没有创建句柄的控件上进行调用。另外,我没有UI:我的对象可能是在Web服务器,服务或控制台上创建的,我不希望出现图形控件 - 我也不想编译对System.Windows的依赖。形式。


我考虑让我的对象公开一个ISynchronizeInvoke接口,但是我需要实现.Invoke(),这就是我的问题。


某些事件的线程A信号线程B的正确技巧是什么,没有线程B被阻塞等待事件发生?

7 个答案:

答案 0 :(得分:11)

这是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 :(得分:3)

我在这里结合了一些回复。

理想情况使用线程安全标志,例如AutoResetEvent。当你调用WaitOne()时,你不必无限期地阻塞,实际上它有一个允许你指定超时的重载。如果在间隔期间未设置标志,则此重载将返回false

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使生成器成为自己的类。生产者默认将项目存储在私有缓冲区中,然后尝试将它们写入公共缓冲区。当使用者使用公共缓冲区时,它会重置生成器对象上的标志。在生产者尝试将项目从私有缓冲区移动到公共缓冲区之前,它会检查此标志,并仅在消费者未处理项目时复制项目。

答案 2 :(得分:1)

如果使用后台工作程序启动第二个线程并使用ProgressChanged事件通知另一个线程数据已准备就绪。其他活动也是如此。 THis MSDN article should get you started

答案 3 :(得分:1)

有很多方法可以做到这一点,具体取决于你想要做什么。 producer/consumer queue可能就是你想要的。要深入了解主题,请参阅优秀书籍Threading中的C# 3.0 in a Nutshell一章(可在线获取)。

答案 4 :(得分:1)

您可以使用AutoResetEvent(或ManualResetEvent)。如果您使用AutoResetEvent.WaitOne(0,false),它将不会阻止。例如:

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

答案 5 :(得分:1)

在这种情况下,BackgroundWorker类就是答案。它是唯一能够异步发送消息到创建BackgroundWorker对象的线程的线程构造。内部BackgroundWorker通过调用AsyncOperation方法使用asyncOperation.Post()类。

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

.NET框架中的一些其他类也使用AsyncOperation:

  • BackgroundWorker的
  • SoundPlayer.LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DownloadFile()
  • WebClient.DownloadFileAsync()
  • Web客户端...
  • PictureBox.LoadAsync()

答案 6 :(得分:0)

如果您的“主”线程是Windows消息泵(GUI)线程,那么您可以使用Forms.Timer进行轮询 - 根据您需要让GUI线程“注意”来自的数据来调整计时器间隔工人线程。

如果您要使用List<>,请务必同步对共享foreach的访问权限,以避免CollectionModified例外。

我在实时交易应用程序中使用这种技术进行所有市场数据驱动的GUI更新,并且它运行良好。