.NET: How to have background thread signal main thread data is available?

后端 未结 7 1966
终归单人心
终归单人心 2021-02-14 04:54

What is the proper technique to have ThreadA signal ThreadB of some event, without having ThreadB sit blocked waiting for an e

7条回答
  •  太阳男子
    2021-02-14 05:34

    I'm combining a few responses here.

    The ideal situation uses a thread-safe flag such as an AutoResetEvent. You don't have to block indefinitely when you call WaitOne(), in fact it has an overload that allows you to specify a timeout. This overload returns false if the flag was not set during the interval.

    A Queue is a more ideal structure for a producer/consumer relationship, but you can mimic it if your requirements are forcing you to use a List. The major difference is you're going to have to ensure your consumer locks access to the collection while it's extracting items; the safest thing is to probably use the CopyTo method to copy all elements to an array, then release the lock. Of course, ensure your producer won't try to update the List while the lock is held.

    Here's a simple C# console application that demonstrates how this might be implemented. If you play around with the timing intervals you can cause various things to happen; in this particular configuration I was trying to have the producer generate multiple items before the consumer checks for items.

    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 _list;
    
            static void Main(string[] args)
            {
                _list = new Queue();
                _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));
                    }
                }
            }
        }
    }
    

    If you don't want to block the producer at all, it's a little more tricky. In this case, I'd suggest making the producer its own class with both a private and a public buffer and a public AutoResetEvent. The producer will by default store items in the private buffer, then try to write them to the public buffer. When the consumer is working with the public buffer, it resets the flag on the producer object. Before the producer tries to move items from the private buffer to the public buffer, it checks this flag and only copies items when the consumer isn't working on it.

提交回复
热议问题