The situation is as follows: There is a class:

public class Person { private readonly AutoResetEvent _autoEvent = new AutoResetEvent(false); public void Eat() { _taskFactory.StartNew(StartEat); autoEvent.WaitOne() } private void StartEat() { //КакиС-Ρ‚ΠΎ дСйствия Π½Π° протяТСнии 10 сСкунд ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ autoEvent.Set(); } } public class Manager { public void Manage() { person.Eat(); } } 

There are 2 managers (Manager) and both have a link to the same Person (person). How to make it so that with a simultaneous command to start eating (eat) a person, both managers wait until he eats and returns control. Both managers live in their own streams. Now it turns out that each of the managers will make their own WaitOne, but autoEvent.Set () will be only once for the thread that the first command has given. Is it possible to do without an event?

  • About the flag that the person is already eating - it is understandable, he did not make it easy. But the problem is still the same, with WaitOne () from different threads. That is, the second manager must wait for the person to finish eating and after that only management can be returned to him - Sleeeper
  • And what is the usual lock does not fit? Locked object the rest of the threads will be waiting. - Luchunpen
  • and what to lochit? is everything in the eat method? The second stream will then force the person to eat again, and he should just wait until she has finished this time - Sleeeper
  • And what does "both managers wait while he eats"? And how do they know that he began to eat? Give the desired order of calls. - VladD
  • Yes, and what should the manager do at the moment when Person doesn't eat? - VladD

2 answers 2

Try, for example, like this:

 object mutex = new object(); bool isRunning = false; // AutoResetEvent Π½Π΅ Π½ΡƒΠΆΠ΅Π½ public void Eat() { lock (mutex) { if (isRunning) // ΡƒΠΆΠ΅ Сст { // Π·Π°Ρ‰ΠΈΡ‚Π° ΠΎΡ‚ spurious wakeup ΠΏΠΎ ΠΈΠ΄Π΅Π΅ Π½Π΅ Π½ΡƒΠΆΠ½Π° Monitor.Wait(mutex); // Π½Π°Π΄ΠΎ Π΄ΠΎΠΆΠ΄Π°Ρ‚ΡŒΡΡ ΠΊΠΎΠ½Ρ†Π° return; // ΠΈ Π²Ρ‹ΠΉΡ‚ΠΈ } isRunning = true; // ΠΈΠ½Π°Ρ‡Π΅ Π½Π°Ρ‡ΠΈΠ½Π°Π΅ΠΌ Π΅ΡΡ‚ΡŒ } // отпускаСм Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΡƒ, ΠΎΠ½Π° Ρ‚ΡƒΡ‚ Π½Π΅ Π½ΡƒΠΆΠ½Π° //КакиС-Ρ‚ΠΎ дСйствия Π½Π° протяТСнии 10 сСкунд ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ lock (mutex) // устанавливаСм Ρ„Π»Π°Π³, Ρ‡Ρ‚ΠΎ ΠΌΡ‹ Π½Π΅ Π΅Π΄ΠΈΠΌ { isRunning = false; Monitor.PulseAll(mutex); // рассылаСм сигнал всСм ΠΈΠ½Ρ‚Π΅Ρ€Π΅ΡΡƒΡŽΡ‰ΠΈΠΌΡΡ } } 

As @Pavel Mayorov rightly says, if you have exceptions during the work process, it makes sense to unlock inside finally :

 try { //КакиС-Ρ‚ΠΎ дСйствия Π½Π° протяТСнии 10 сСкунд ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ } finally { // Π΄Π°ΠΆΠ΅ Ссли Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»ΠΎΡΡŒ с ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ΠΌ, ΠΌΡ‹ Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΠΌ этот кусок lock (mutex) // устанавливаСм Ρ„Π»Π°Π³, Ρ‡Ρ‚ΠΎ ΠΌΡ‹ Π½Π΅ Π΅Π΄ΠΈΠΌ { isRunning = false; Monitor.PulseAll(mutex); // рассылаСм сигнал всСм ΠΈΠ½Ρ‚Π΅Ρ€Π΅ΡΡƒΡŽΡ‰ΠΈΠΌΡΡ } } 
  • Isn't it easier to enter a state variable and check it, for example, Interlocked? - Luchunpen
  • @Luchunpen: Under lock 'I have a guarantee that as long as I hold lock , no one will change the variable. Imagine that stream # 1 checked, saw false. The context went to stream number 2, he checked and saw false. Now both start long actions at the same time. Oops. - VladD 8:19 pm
  • @Luchunpen: And if you will be through Interlocked.Exchange , you still need to somehow wait for the run of another stream. This is either Monitor , or AutoResetEvent again. If AutoResetEvent , where is the guarantee that the event won't fire between my check and the start of the wait? With lock 's warranty is. And if the Monitor , then it again needs mutex . - VladD
  • @VladD The second lock must be in the finally block - otherwise everything will hang first error ... - Pavel Mayorov
  • @PavelMayorov: Well, yes, or swallow all the exceptions in the payload. - VladD

For the sake of completeness, I will give a solution on the problems:

 private volatile Task eatTask; public void Eat() { var localTask = eatTask; if (localTask != null) { localTask.Wait(); return; } var tcs = new TaskCompletionSource<object>(); localTask = Interlocked.CompareExchange(ref eatTask, tcs.Task, null); if (localTask != null) { localTask.Wait(); return; } try { EatImpl(); tcs.SetResult(null); } catch (Exception ex) { tcs.SetException(ex); throw; } finally { Debug.Assert(eatTask == tcs.Task); eatTask = null; } } private void EatImpl() { //КакиС-Ρ‚ΠΎ дСйствия Π½Π° протяТСнии 10 сСкунд ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ } 

Immediately I warn you - this decision has no particular merits. The only difference is the fact that if an exception is thrown, it will be extended to pending threads. But the same effect can be achieved easier.