多线程编程,两个线程间的通信 C#

13

我想知道在两个线程之间实现通信的最佳方法是什么。我有一个生成随机数的线程(Sender类),现在我想要另一个接收生成的随机数的线程(Receiver类)。

这是Sender:

public  class Sender
{
    public int GenerateNumber(){


        //some code
        return randomNumber;
    }
}

当然,在主函数中我会启动那些线程:

static void Main(string[] args){

     Sender _sender=new Sender();
     Thread thread1=new Thread(new ThreadStart(_sender.GenerateNumber));

}

感谢你的帮助


这两个线程同时开始吗?当接收器等待发送器时,它在做什么?发送器生成随机数后会做什么? - cadrell0
在主函数中,我将启动两个线程,发送线程将工作,然后休眠3秒钟。同时,我将启动一个接收器实例来接收数据...基本上,线程将一直工作,直到用户按下ESC键...接收器将在控制台上写入接收到的数字。 - Avicena00
是的,这是.NET 4,谢谢:) - Avicena00
让我来帮你处理GenerateNumber方法的实现int randomNumber = 4; // chosen by fair dice roll. - Dr. Wily's Apprentice
好的观点,但这只是我正在尝试实现的基本方法,只是为了能够在线程之间进行通信,在发送线程之后会有更复杂的方法 :) - Avicena00
5个回答

22
如果你正在使用.NET 4,我建议使用更高级别的抽象:Task<TResult>。您的第一个线程可以调度任务(可能会创建一个线程,或被调度到现有的任务处理线程上),然后可以根据需要检查状态、阻塞结果等。
如果您想要执行多个任务,您可能需要使用生产者/消费者队列--同样,.NET 4通过BlockingCollection<T>帮助实现这一点。

1
我知道我必须深入阅读C#,所以我今晚就开始 :) - Avicena00
1
@AintMeBabe:说实话,《C#深入浅出》几乎没有涉及新的Task<T>内容... 通过MSDN博客文章研究任务并行库可能比阅读这本书更有直接的好处。当然,我并不会长期阻止你 :) - Jon Skeet
如果我们无法控制生产者怎么办?在我的情况下,线程A(在我控制之下)执行某些操作,并且需要等待线程B引发事件;线程B由库生成。我监听该事件。事件侦听器(在线程B中运行)应如何通知线程A?除了ManualResetEvent,我想不到其他方法。在.NET 4中是否有更好的方法? - Jimmy
1
@Jimmy:我建议你用一个 [mcve] 提出一个新问题。 - Jon Skeet

10

以下是一种可能的方法,使用WaitHandle:

class Program
{
    static void Main(string[] args)
    {
        Sender _sender = new Sender();
        Receiver _receiver = new Receiver();

        using (ManualResetEvent waitHandle = new ManualResetEvent(false))
        {
            // have to initialize this variable, otherwise the compiler complains when it is used later
            int randomNumber = 0;

            Thread thread1 = new Thread(new ThreadStart(() =>
            {
                randomNumber = _sender.GenerateNumber();

                try
                {
                    // now that we have the random number, signal the wait handle
                    waitHandle.Set();
                }
                catch (ObjectDisposedException)
                {
                    // this exception will be thrown if the timeout elapses on the call to waitHandle.WaitOne
                }
            }));

            // begin receiving the random number
            thread1.Start();

            // wait for the random number
            if (waitHandle.WaitOne(/*optionally pass in a timeout value*/))
            {
                _receiver.TakeRandomNumber(randomNumber);
            }
            else
            {
                // signal was never received
                // Note, this code will only execute if a timeout value is specified
                System.Console.WriteLine("Timeout");
            }
        }
    }
}

public class Sender
{
    public int GenerateNumber()
    {
        Thread.Sleep(2000);

        // http://xkcd.com/221/
        int randomNumber = 4; // chosen by fair dice role

        return randomNumber;
    }
}

public class Receiver
{
    public void TakeRandomNumber(int randomNumber)
    {
        // do something
        System.Console.WriteLine("Received random number: {0}", randomNumber);
    }
}


我只是想更新我的答案,提供使用.NET 4中Jon Skeet在他的回答中指出的Task<TResult>类的等效代码。感谢他指出这一点。非常感谢你,Jon。我之前没有使用过这个类,并且当我看到它有多么容易使用时,感到非常惊喜。

除了在底层获得性能优势之外,使用Task<TResult>类编写等效代码似乎更加简单。例如,上面Main方法的主体可以重写如下:

        Sender _sender = new Sender();
        Receiver _receiver = new Receiver();

        Task<int> getRandomNumber = Task.Factory.StartNew<int>(_sender.GenerateNumber);

        // begin receiving the random number
        getRandomNumber.Start();

        // ... perform other tasks

        // wait for up to 5 seconds for the getRandomNumber task to complete
        if (getRandomNumber.Wait(5000))
        {
            _receiver.TakeRandomNumber(getRandomNumber.Result);
        }
        else
        {
            // the getRandomNumber task did not complete within the specified timeout
            System.Console.WriteLine("Timeout");
        }

如果您不需要为任务指定超时并愿意无限期地等待它完成,那么您可以使用更少的代码编写:

        Sender _sender = new Sender();
        Receiver _receiver = new Receiver();

        Task<int> getRandomNumber = Task.Factory.StartNew<int>(_sender.GenerateNumber);

        // begin receiving the random number
        getRandomNumber.Start();

        // ... perform other tasks

        // accessing the Result property implicitly waits for the task to complete
        _receiver.TakeRandomNumber(getRandomNumber.Result);

@AintMeBabe - 如果你正在使用.NET 4,那么我必须推荐你查看Jon Skeet的答案。看起来你可以使用Task<TResult>类以更少的代码完成相同的事情。 - Dr. Wily's Apprentice

3
您需要一些资源(列表、队列等),这些资源需要在发送方和接收方之间共享。同时,您还需要同步访问这些资源,否则线程之间无法传递数据。

2

实现两个线程之间的通信,最好的方法取决于需要通信的内容。你的例子似乎是一个经典的生产者/消费者问题。我建议使用同步队列。请参考MSDN文档中的同步集合。你可以使用Queue.Synchronized方法来获取一个Queue对象的同步包装器。然后,生产者线程调用Enqueue(),消费者线程调用Dequeue()。


0
如果你只是在一个线程中生成随机数,我建议创建一个线程安全的对象来代替。
lock(syncRoot)
{
    myCurrentRandom = Generate();
    return myCurrentRandom;
}

好的,这只是我正在测试的一个函数,以使事情正常工作,在线程发送者之后,还会有更多的方法。 - Avicena00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接