gpt4 book ai didi

c# - 如何使用 Monitor/Mutex/Semaphore 同步 TPL 任务?还是应该完全使用其他东西?

转载 作者:太空狗 更新时间:2023-10-30 00:49:53 25 4
gpt4 key购买 nike

我正在尝试从 ThreadPool 移动我的一些旧项目和独立 Thread到 TPL Task ,因为它支持一些非常方便的特性,比如与 Task.ContinueWith 的延续。 (以及来自 C# 5 的 async\await )、更好的取消、异常捕获等。我很想在我的项目中使用它们。但是我已经看到了潜在的问题,主要是同步问题。

我写了一些代码来显示生产者/消费者问题,使用经典的独立 Thread :

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);
}
}
}

在本例中, ProduceNumber生成一个递增的整数序列,而 ConsumeNumber将它们写入 Console .如果生产运行得更快,数字将排队等待稍后消费。如果消费运行得更快,则消费者将等到一个数字可用。所有同步都是使用 Monitor 完成的和 lock (内部也是 Monitor )。

在尝试“TPL-ify”类似代码时,我已经看到了一些我不知道如何解决的问题。如果我更换 new Thread().Start()Task.Run() :
  • TPL Task是一种抽象,它甚至不能保证代码将在单独的线程上运行。在我的例子中,如果生产者控制方法同步运行,无限循环将导致消费者甚至永远不会启动。根据MSDN,提供TaskCreationOptions.LongRunning运行任务时的参数应该提示 TaskScheduler适本地运行该方法,但是我没有找到任何方法来确保它确实如此。据说 TPL 足够聪明,可以按照程序员预期的方式运行任务,但这对我来说似乎有点神奇。而且我不喜欢编程中的魔法。
  • 如果我理解这是如何正确工作的,TPL Task不能保证在启动时在同一线程上恢复。如果是这样,在这种情况下,它会尝试释放不属于它的锁,而另一个线程永远持有该锁,从而导致死锁。我记得不久前 Eric Lippert 写道,这就是为什么 await 的原因。不允许在 lock 中堵塞。回到我的例子,我什至不确定如何解决这个问题。

  • 这些是我想到的少数问题,尽管可能(可能还有)更多。我应该如何解决它们?

    此外,这让我想到,正在使用通过 Monitor 进行同步的经典方法。 , MutexSemaphore甚至正确的方法来做 TPL 代码?也许我错过了我应该使用的东西?

    最佳答案

    您的问题突破了 Stack Overflow 的广泛性限制。从平原搬家 Thread基于 Task 的东西的实现和其他 TPL 功能涉及多种考虑因素。单独来看,几乎可以肯定每个问题都在之前的 Stack Overflow 问答中得到了解决,总的来说,在单个 Stack Overflow 问答中需要充分和全面地解决太多的考虑因素。

    因此,话虽如此,让我们来看看您在这里提出的具体问题。

    1. TPL Task is an abstraction, which does not even guarantee that the code will run on a separate thread. In my example, if the producer control method runs synchronously, the infinite loop will cause the consumer to never even start. According to MSDN, providing a TaskCreationOptions.LongRunning parameter when running the task should hint the TaskScheduler to run the method appropriately, however I didn't find any way to ensure that it does. Supposedly TPL is smart enough to run tasks the way the programmer intended, but that just seems like a bit of magic to me. And I don't like magic in programming.


    确实, Task对象本身不保证异步行为。例如,一个 async返回 Task 的方法对象可以根本不包含异步操作,并且可以在返回一个已经完成的 Task 之前运行很长时间。目的。

    另一方面, Task.Run()保证异步操作。是 documented as such :

    Queues the specified work to run on the ThreadPool and returns a task or Task<TResult> handle for that work



    Task对象本身抽象了“ future ”或“ promise ”的概念(使用编程中的同义词),具体实现与线程池密切相关。如果使用得当,您可以放心进行异步操作。

    1. If I understand how this works correctly, a TPL Task is not guaranteed to resume on the same thread as it started. If it does, in this case it would try to release a lock it doesn't own while the other thread holds the lock forever, resulting in a deadlock. I remember a while ago Eric Lippert writing that it's the reason why await is not allowed in a lock block. Going back to my example, I'm not even sure how to go about solving this issue.


    只有一些同步对象是线程特定的。例如, Monitor是。但是 Semaphore不是。这对您是否有用取决于您要实现的目标。例如,您可以使用使用 BlockingCollection<T> 的长时间运行的线程来实现生产者/消费者模式。 ,根本不需要调用任何显式同步对象。如果你确实想使用 TPL 技术,你可以使用 SemaphoreSlim和它的 WaitAsync()方法。

    当然,您也可以使用 Dataflow API。对于某些情况,这将是可取的。对于非常简单的生产者/消费者来说,这可能是矫枉过正。 :)

    Also, this made me think, is using the classical approach of synchronizing via Monitor, Mutex or Semaphore even the right way to do TPL code? Perhaps I'm missing something that I should be using instead?



    恕我直言,这是问题的关键。搬家 Thread基于 TPL 的编程不仅仅是从一种构造到另一种构造的直接映射。在某些情况下,这样做效率低下,而在其他情况下,它根本行不通。

    事实上,我会说 TPL 的一个关键特征,尤其是 async/ await是线程同步的必要性要小得多。一般的想法是异步执行操作,线程之间的交互最少。数据仅在明确定义的点(即从完成的 Task 对象中检索)在线程之间流动,从而减少甚至消除了显式同步的需要。

    不可能建议具体的技术,因为如何最好地实现某事将取决于具体的目标是什么。但是简短的版本是要理解,在使用 TPL 时,通常根本没有必要使用同步原语,例如您习惯于与较低级别的 API 一起使用的原语。您应该努力获得足够的 TPL 习语经验,以便您可以识别哪些适用于哪些编程问题,以便您直接应用它们,而不是试图在脑海中映射您的旧知识。

    在某种程度上,这(我认为)类似于学习一种新的人类语言。一开始,人们花费大量时间进行字面翻译,可能会重新映射以适应语法、习语等。但理想情况下,在某个时刻,人们将语言内化并能够直接用该语言表达自己。就我个人而言,当谈到人类语言时,我从未达到过这一点,但我在理论上理解这个概念:)。我可以直接告诉你,它在编程语言的上下文中工作得很好。

    顺便说一下,如果您有兴趣了解极端化的 TPL 想法是如何实现的,您可能希望通读 Joe Duffy's recent blog articles关于这个话题。事实上,.NET 和相关语言的最新版本大量借鉴了他所描述的 Midori 项目中开发的概念。

    关于c# - 如何使用 Monitor/Mutex/Semaphore 同步 TPL 任务?还是应该完全使用其他东西?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35142942/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com