C# 如何使用监视器/互斥体/信号量同步TPL任务?还是应该完全使用其他东西?

C# 如何使用监视器/互斥体/信号量同步TPL任务?还是应该完全使用其他东西?,c#,.net,multithreading,synchronization,task-parallel-library,C#,.net,Multithreading,Synchronization,Task Parallel Library,我正在尝试将我的一些旧项目从线程池和独立线程移动到TPL任务,因为它支持一些非常方便的功能,比如继续执行任务。继续执行(从C#5执行异步\等待),更好的取消和异常捕获,等等。我很乐意在我的项目中使用它们。但是,我已经看到了潜在的问题,主要是同步问题 我已经用一个经典的独立线程编写了一些代码,显示了生产者/消费者的问题: class ThreadSynchronizationTest { private int CurrentNumber { get; set; } private

我正在尝试将我的一些旧项目从
线程池
和独立
线程
移动到TPL
任务
,因为它支持一些非常方便的功能,比如继续执行
任务。继续执行
(从C#5执行
异步\等待
),更好的取消和异常捕获,等等。我很乐意在我的项目中使用它们。但是,我已经看到了潜在的问题,主要是同步问题

我已经用一个经典的独立
线程编写了一些代码,显示了生产者/消费者的问题:

class ThreadSynchronizationTest
{
    private int CurrentNumber { get; set; }
    private object Synchro { get; set; }
    private Queue<int> WaitingNumbers { get; set; }

    public void TestSynchronization()
    {
        Synchro = new object();
        WaitingNumbers = new Queue<int>();

        var producerThread = new Thread(RunProducer);
        var consumerThread = new Thread(RunConsumer);

        producerThread.Start();
        consumerThread.Start();

        producerThread.Join();
        consumerThread.Join();
    }

    private int ProduceNumber()
    {
        CurrentNumber++;
        // Long running method. Sleeping as an example
        Thread.Sleep(100);
        return CurrentNumber;
    }

    private void ConsumeNumber(int number)
    {
        Console.WriteLine(number);
        // Long running method. Sleeping as an example
        Thread.Sleep(100);
    }

    private void RunProducer()
    {
        while (true)
        {
            int producedNumber = ProduceNumber();

            lock (Synchro)
            {
                WaitingNumbers.Enqueue(producedNumber);
                // Notify consumer about a new number
                Monitor.Pulse(Synchro);
            }
        }
    }

    private void RunConsumer()
    {
        while (true)
        {
            int numberToConsume;
            lock (Synchro)
            {
                // Ensure we met out wait condition
                while (WaitingNumbers.Count == 0)
                {
                    // Wait for pulse
                    Monitor.Wait(Synchro);
                }
                numberToConsume = WaitingNumbers.Dequeue();
            }
            ConsumeNumber(numberToConsume);
        }
    }
}
类线程同步测试
{
private int CurrentNumber{get;set;}
私有对象同步{get;set;}
专用队列等待编号{get;set;}
公共void TestSynchronization()
{
Synchro=新对象();
WaitingNumbers=新队列();
var producerThread=新线程(RunProducer);
var consumerThread=新线程(RunConsumer);
producerThread.Start();
consumerThread.Start();
producerThread.Join();
consumerThread.Join();
}
私有int ProduceNumber()
{
CurrentNumber++;
//长时间运行的方法。以睡眠为例
睡眠(100);
返回当前编号;
}
专用编号(整数)
{
控制台写入线(编号);
//长时间运行的方法。以睡眠为例
睡眠(100);
}
私有void RunProducer()
{
while(true)
{
int producedNumber=ProduceNumber();
锁(同步)
{
等待编号。排队(生产编号);
//通知消费者新号码
监视器。脉冲(同步);
}
}
}
私有用户()
{
while(true)
{
整数消费;
锁(同步)
{
//确保我们满足了等待条件
while(WaitingNumbers.Count==0)
{
//等待脉搏
监视器。等待(同步);
}
numberToConsume=WaitingNumbers.Dequeue();
}
消费者编号(numberToConsume);
}
}
}
在本例中,
ProduceNumber
生成一系列递增的整数,而
consumernumber
将它们写入
控制台
。如果生产运行得更快,则数字将排队等待以后使用。如果消费速度加快,消费者将等待一个数字。使用
监视器
(内部也使用
监视器
)完成所有同步

在尝试“第三方物流化”类似的代码时,我已经看到了一些问题,我不知道如何处理。如果我将
new Thread().Start()
替换为
Task.Run()

  • TPL
    Task
    是一种抽象,它甚至不能保证代码将在单独的线程上运行。在我的示例中,如果生产者控制方法同步运行,无限循环将导致消费者甚至从未启动。根据MSDN,在运行任务时提供
    TaskCreationOptions.LongRunning
    参数应该提示
    TaskScheduler
    正确运行该方法,但是我没有找到任何方法来确保它。据说TPL足够聪明,可以按照程序员的预期方式运行任务,但这对我来说似乎有点神奇。我不喜欢编程中的魔法
  • 如果我理解这是如何正确工作的,那么TPL
    任务
    就不能保证在启动时在同一线程上恢复。如果它这样做了,在这种情况下,它将尝试释放一个它不拥有的锁,而另一个线程将永远持有该锁,从而导致死锁。我记得不久前Eric Lippert写道,这就是为什么
    块中不允许
    等待
    的原因。回到我的例子,我甚至不知道如何着手解决这个问题
  • 这些是我脑海中闪过的少数几个问题,尽管可能(可能)还有更多。我应该如何着手解决这些问题


    此外,这让我想到,使用经典的同步方法,通过
    监视器
    互斥体
    信号量
    进行同步,这是不是做TPL代码的正确方法?也许我遗漏了应该使用的内容?

    您的问题突破了堆栈溢出的宽度限制。从普通的
    Thread
    实现转移到基于
    Task
    和其他TPL功能的实现涉及到各种各样的考虑。单独来看,几乎可以肯定,在之前的堆栈溢出Q&a中已经解决了每一个问题,并且总体来看,在单个堆栈溢出Q&a中,有太多的考虑因素需要充分和全面地解决

    话虽如此,让我们看看你在这里问的具体问题

  • TPL任务是一种抽象,它甚至不能保证代码将在单独的线程上运行。在我的示例中,如果生产者控制方法同步运行,无限循环将导致消费者甚至从未启动。根据MSDN,在运行任务时提供
    TaskCreationOptions.LongRunning
    参数应该提示
    TaskScheduler
    正确运行该方法,但是我没有找到任何方法来确保它正确运行。据说TPL足够聪明,可以按照程序员的预期方式运行任务,但这对我来说似乎有点神奇。我不喜欢编程中的魔法
  • 确实,
    任务
    对象本身并不保证异步行为。例如,一个返回
    任务
    对象的
    async
    方法在同一时间内不能包含任何异步操作